/* * ==================================================================== * 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.io.InputStream; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; 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.SVNMergeRange; import org.tmatesoft.svn.core.SVNMergeRangeList; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.SVNMergeInfoManager; import org.tmatesoft.svn.core.io.ISVNFileRevisionHandler; import org.tmatesoft.svn.core.io.SVNFileRevision; import org.tmatesoft.svn.core.io.SVNLocationEntry; import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class FSFileRevisionsFinder { private FSFS myFSFS; private SVNDeltaGenerator myDeltaGenerator; public FSFileRevisionsFinder(FSFS fsfs) { myFSFS = fsfs; } public int getFileRevisions(String path, long startRevision, long endRevision, boolean includeMergedRevisions, ISVNFileRevisionHandler handler) throws SVNException { Map duplicatePathRevs = new SVNHashMap(); LinkedList mainLinePathRevisions = findInterestingRevisions(null, path, startRevision, endRevision, includeMergedRevisions, false, duplicatePathRevs); LinkedList mergedPathRevisions = null; if (includeMergedRevisions) { mergedPathRevisions = findMergedRevisions(mainLinePathRevisions, duplicatePathRevs); } else { mergedPathRevisions = new LinkedList(); } SVNErrorManager.assertionFailure(!mainLinePathRevisions.isEmpty(), "no main line path revisions found", SVNLogType.FSFS); SendBaton sb = new SendBaton(); sb.myLastProps = new SVNProperties(); int mainLinePos = mainLinePathRevisions.size() - 1; int mergedPos = mergedPathRevisions.size() - 1; int i = 0; while (mainLinePos >= 0 && mergedPos >= 0) { SVNLocationEntry mainPathRev = (SVNLocationEntry) mainLinePathRevisions.get(mainLinePos); SVNLocationEntry mergedPathRev = (SVNLocationEntry) mergedPathRevisions.get(mergedPos); if (mainPathRev.getRevision() <= mergedPathRev.getRevision()) { sendPathRevision(mainPathRev, sb, handler); mainLinePos--; } else { sendPathRevision(mergedPathRev, sb, handler); mergedPos--; } i++; } for (; mainLinePos >= 0; mainLinePos--) { SVNLocationEntry mainPathRev = (SVNLocationEntry) mainLinePathRevisions.get(mainLinePos); sendPathRevision(mainPathRev, sb, handler); i++; } return i; } private void sendPathRevision(SVNLocationEntry pathRevision, SendBaton sendBaton, ISVNFileRevisionHandler handler) throws SVNException { SVNProperties revProps = myFSFS.getRevisionProperties(pathRevision.getRevision()); FSRevisionRoot root = myFSFS.createRevisionRoot(pathRevision.getRevision()); FSRevisionNode fileNode = root.getRevisionNode(pathRevision.getPath()); SVNProperties props = fileNode.getProperties(myFSFS); SVNProperties propDiffs = FSRepositoryUtil.getPropsDiffs(sendBaton.myLastProps, props); boolean contentsChanged = false; if (sendBaton.myLastRoot != null) { contentsChanged = FSRepositoryUtil.areFileContentsChanged(sendBaton.myLastRoot, sendBaton.myLastPath, root, pathRevision.getPath()); } else { contentsChanged = true; } if (handler != null) { handler.openRevision(new SVNFileRevision(pathRevision.getPath(), pathRevision.getRevision(), revProps, propDiffs, pathRevision.isResultOfMerge())); if (contentsChanged) { SVNDeltaCombiner sourceCombiner = new SVNDeltaCombiner(); SVNDeltaCombiner targetCombiner = new SVNDeltaCombiner(); handler.applyTextDelta(pathRevision.getPath(), null); InputStream sourceStream = null; InputStream targetStream = null; try { if (sendBaton.myLastRoot != null && sendBaton.myLastPath != null) { sourceStream = sendBaton.myLastRoot.getFileStreamForPath(sourceCombiner, sendBaton.myLastPath); } else { sourceStream = FSInputStream.createDeltaStream(sourceCombiner, (FSRevisionNode) null, myFSFS); } targetStream = root.getFileStreamForPath(targetCombiner, pathRevision.getPath()); SVNDeltaGenerator deltaGenerator = getDeltaGenerator(); deltaGenerator.sendDelta(pathRevision.getPath(), sourceStream, 0, targetStream, handler, false); } finally { SVNFileUtil.closeFile(sourceStream); SVNFileUtil.closeFile(targetStream); } handler.closeRevision(pathRevision.getPath()); } else { handler.closeRevision(pathRevision.getPath()); } } sendBaton.myLastRoot = root; sendBaton.myLastPath = pathRevision.getPath(); sendBaton.myLastProps = props; } private SVNDeltaGenerator getDeltaGenerator() { if (myDeltaGenerator == null) { myDeltaGenerator = new SVNDeltaGenerator(); } return myDeltaGenerator; } private LinkedList findMergedRevisions(LinkedList mainLinePathRevisions, Map duplicatePathRevs) throws SVNException { LinkedList mergedPathRevisions = new LinkedList(); LinkedList oldPathRevisions = mainLinePathRevisions; LinkedList newPathRevisions = null; do { newPathRevisions = new LinkedList(); for (Iterator oldPathRevsIter = oldPathRevisions.iterator(); oldPathRevsIter.hasNext();) { SVNLocationEntry oldPathRevision = (SVNLocationEntry) oldPathRevsIter.next(); Map mergedMergeInfo = oldPathRevision.getMergedMergeInfo(); if (mergedMergeInfo == null) { continue; } for (Iterator mergeInfoIter = mergedMergeInfo.keySet().iterator(); mergeInfoIter.hasNext();) { String path = (String) mergeInfoIter.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) mergedMergeInfo.get(path); SVNMergeRange[] ranges = rangeList.getRanges(); for (int j = 0; j < ranges.length; j++) { SVNMergeRange range = ranges[j]; FSRevisionRoot root = myFSFS.createRevisionRoot(range.getEndRevision()); SVNNodeKind kind = root.checkNodeKind(path); if (kind != SVNNodeKind.FILE) { continue; } newPathRevisions = findInterestingRevisions(newPathRevisions, path, range.getStartRevision(), range.getEndRevision(), true, true, duplicatePathRevs); } } } mergedPathRevisions.addAll(newPathRevisions); oldPathRevisions = newPathRevisions; } while (!newPathRevisions.isEmpty()); Collections.sort(mergedPathRevisions, new Comparator() { public int compare(Object arg0, Object arg1) { SVNLocationEntry pathRevision1 = (SVNLocationEntry) arg0; SVNLocationEntry pathRevision2 = (SVNLocationEntry) arg1; if (pathRevision1.getRevision() == pathRevision2.getRevision()) { return 0; } return pathRevision1.getRevision() < pathRevision2.getRevision() ? 1 : -1; } }); return mergedPathRevisions; } private LinkedList findInterestingRevisions(LinkedList pathRevisions, String path, long startRevision, long endRevision, boolean includeMergedRevisions, boolean markAsMerged, Map duplicatePathRevs) throws SVNException { FSRevisionRoot root = myFSFS.createRevisionRoot(endRevision); if (root.checkNodeKind(path) != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "''{0}'' is not a file in revision ''{1}''", new Object[] { path, new Long(endRevision) }); SVNErrorManager.error(err, SVNLogType.FSFS); } pathRevisions = pathRevisions == null ? new LinkedList() : pathRevisions; FSNodeHistory history = root.getNodeHistory(path); while (true) { history = history.getPreviousHistory(true); if (history == null) { break; } long histRev = history.getHistoryEntry().getRevision(); String histPath = history.getHistoryEntry().getPath(); if (includeMergedRevisions && duplicatePathRevs.containsKey(histPath + ":" + histRev)) { break; } SVNLocationEntry pathRev = null; Map mergedMergeInfo = null; if (includeMergedRevisions) { mergedMergeInfo = getMergedMergeInfo(histPath, histRev); pathRev = new SVNLocationEntry(histRev, histPath, markAsMerged, mergedMergeInfo); duplicatePathRevs.put(histPath + ":" + histRev, pathRev); } else { pathRev = new SVNLocationEntry(histRev, histPath, markAsMerged, null); } pathRevisions.addLast(pathRev); if (histRev <= startRevision) { break; } } return pathRevisions; } private Map getMergedMergeInfo(String path, long revision) throws SVNException { Map currentMergeInfo = getPathMergeInfo(path, revision); Map previousMergeInfo = null; try { previousMergeInfo = getPathMergeInfo(path, revision - 1); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) { previousMergeInfo = new TreeMap(); } else { throw svne; } } Map deleted = new TreeMap(); Map changed = new TreeMap(); SVNMergeInfoUtil.diffMergeInfo(deleted, changed, previousMergeInfo, currentMergeInfo, false); changed = SVNMergeInfoUtil.mergeMergeInfos(changed, deleted); return changed; } public Map getPathMergeInfo(String path, long revision) throws SVNException { SVNMergeInfoManager mergeInfoManager = new SVNMergeInfoManager(); FSRevisionRoot root = myFSFS.createRevisionRoot(revision); Map tmpMergeInfo = mergeInfoManager.getMergeInfo(new String[] { path }, root, SVNMergeInfoInheritance.INHERITED, false); SVNMergeInfo mergeInfo = (SVNMergeInfo) tmpMergeInfo.get(path); if (mergeInfo != null) { return mergeInfo.getMergeSourcesToMergeLists(); } return new TreeMap(); } private static class SendBaton { private FSRevisionRoot myLastRoot; private String myLastPath; private SVNProperties myLastProps; } }