package org.tmatesoft.svn.core.internal.wc2.ng; import java.io.File; import java.util.*; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils; import org.tmatesoft.svn.core.internal.wc17.db.Structure; import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess; import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver; import org.tmatesoft.svn.core.wc2.SvnLog; import org.tmatesoft.svn.core.wc2.SvnLogMergeInfo; import org.tmatesoft.svn.core.wc2.SvnOperationFactory; import org.tmatesoft.svn.core.wc2.SvnRevisionRange; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; public class SvnNgLogMergeInfo extends SvnNgOperationRunner<SVNLogEntry, SvnLogMergeInfo> { @Override public boolean isApplicable(SvnLogMergeInfo operation, SvnWcGeneration wcGeneration) throws SVNException { boolean targetOk = operation.getFirstTarget().isURL() || SvnOperationFactory.detectWcGeneration(operation.getFirstTarget().getFile(), true) == SvnWcGeneration.V17; boolean sourceOk = operation.getSource().isURL() || SvnOperationFactory.detectWcGeneration(operation.getSource().getFile(), true) == SvnWcGeneration.V17; return targetOk && sourceOk; } @Override public SvnWcGeneration getWcGeneration() { return SvnWcGeneration.NOT_DETECTED; } @Override protected SVNLogEntry run(SVNWCContext context) throws SVNException { SVNURL[] root = new SVNURL[1]; if (getOperation().getDepth() != SVNDepth.EMPTY && getOperation().getDepth() != SVNDepth.INFINITY) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Only depths 'infinity' and 'empty' are currently supported"); SVNErrorManager.error(errorMessage, SVNLogType.CLIENT); } Collection<SvnRevisionRange> ranges = getOperation().getRanges(); SVNRevision sourceStartRevision = (ranges == null || ranges.size() == 0) ? SVNRevision.UNDEFINED : ranges.iterator().next().getStart(); SVNRevision sourceEndRevision = (ranges == null || ranges.size() == 0) ? SVNRevision.UNDEFINED : ranges.iterator().next().getEnd(); if (sourceStartRevision.isLocal()) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION); SVNErrorManager.error(errorMessage, SVNLogType.CLIENT); } if (sourceEndRevision.isLocal()) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION); SVNErrorManager.error(errorMessage, SVNLogType.CLIENT); } if (sourceEndRevision != SVNRevision.UNDEFINED && sourceStartRevision == SVNRevision.UNDEFINED) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION); SVNErrorManager.error(errorMessage, SVNLogType.CLIENT); } if (sourceEndRevision == SVNRevision.UNDEFINED && sourceStartRevision != SVNRevision.UNDEFINED) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION); SVNErrorManager.error(errorMessage, SVNLogType.CLIENT); } Map<String, Map<String, SVNMergeRangeList>> targetMergeInfoCatalog = null; Map<String, Map<String, SVNMergeRangeList>> mergeInfoCatalog = null; SVNRepository sourceRepository = null; SVNRepository targetRepository = null; if (targetMergeInfoCatalog != null) { if (targetMergeInfoCatalog.size() == 0) { Structure<SvnRepositoryAccess.RepositoryInfo> repositoryInfo = getRepositoryAccess().createRepositoryFor(getOperation().getFirstTarget(), getOperation().getFirstTarget().getPegRevision(), getOperation().getFirstTarget().getPegRevision(), null); targetRepository = repositoryInfo.get(SvnRepositoryAccess.RepositoryInfo.repository); root[0] = targetRepository.getRepositoryRoot(true); } else { mergeInfoCatalog = SvnNgMergeinfoUtil.getMergeInfo(getWcContext(), getRepositoryAccess(), getOperation().getFirstTarget(), getOperation().getDepth() == SVNDepth.INFINITY, true, root); targetMergeInfoCatalog = mergeInfoCatalog; } } else { mergeInfoCatalog = SvnNgMergeinfoUtil.getMergeInfo(getWcContext(), getRepositoryAccess(), getOperation().getFirstTarget(), getOperation().getDepth() == SVNDepth.INFINITY, true, root); } File reposRelPath = null; SvnTarget target = getOperation().getFirstTarget(); if (!target.isURL()) { reposRelPath = getWcContext().getNodeReposRelPath(getOperation().getFirstTarget().getFile()); } else { reposRelPath = SVNFileUtil.createFilePath(SVNPathUtil.getRelativePath(root[0].getPath(), target.getURL().getPath())); } if (mergeInfoCatalog == null) { if (getOperation().isFindMerged()) { return getOperation().first(); } mergeInfoCatalog = new TreeMap<String, Map<String,SVNMergeRangeList>>(); mergeInfoCatalog.put(SVNFileUtil.getFilePath(reposRelPath), new TreeMap<String, SVNMergeRangeList>()); } Map<String, SVNMergeRangeList> history = null; Map<String, SVNMergeRangeList> sourceHistory = null; if (!getOperation().isFindMerged()) { history = getRepositoryAccess().getHistoryAsMergeInfo(targetRepository, target, -1, -1); } Structure<SvnRepositoryAccess.RepositoryInfo> repositoryInfo = getRepositoryAccess().createRepositoryFor(getOperation().getSource(), getOperation().getSource().getPegRevision(), getOperation().getSource().getPegRevision(), null); sourceRepository = repositoryInfo.get(SvnRepositoryAccess.RepositoryInfo.repository); long pathRevision = repositoryInfo.lng(SvnRepositoryAccess.RepositoryInfo.revision); Structure<SvnRepositoryAccess.RevisionsPair> startRevisionPair = getRepositoryAccess().getRevisionNumber(sourceRepository, getOperation().getSource(), sourceStartRevision, null); long startRevision = startRevisionPair.lng(SvnRepositoryAccess.RevisionsPair.revNumber); Structure<SvnRepositoryAccess.RevisionsPair> endRevisionPair = getRepositoryAccess().getRevisionNumber(sourceRepository, getOperation().getSource(), sourceEndRevision, null); long endRevision = endRevisionPair.lng(SvnRepositoryAccess.RevisionsPair.revNumber); sourceHistory = getRepositoryAccess().getHistoryAsMergeInfo(null, getOperation().getSource(), Math.max(endRevision, startRevision), Math.min(endRevision, startRevision)); boolean oldestRevsFirst = startRevision <= endRevision; String reposRelPathStr = SVNFileUtil.getFilePath(reposRelPath); SVNMergeRangeList masterNonInheritableRangeList = new SVNMergeRangeList((SVNMergeRange[]) null); SVNMergeRangeList masterInheritableRangeList = new SVNMergeRangeList((SVNMergeRange[]) null); Map<String, SVNMergeRangeList> inheritableSubtreeMerges = new TreeMap<String, SVNMergeRangeList>(); for (String subtreePath : mergeInfoCatalog.keySet()) { Map<String, SVNMergeRangeList> subtreeMergeInfo = mergeInfoCatalog.get(subtreePath); Map<String, SVNMergeRangeList> subtreeHistory = null; Map<String, SVNMergeRangeList> subtreeSourceHistory; Map<String, SVNMergeRangeList> subtreeInheritableMergeInfo; Map<String, SVNMergeRangeList> subtreeNonInheritableMergeInfo; Map<String, SVNMergeRangeList> mergedNonInheritableMergeInfo; Map<String, SVNMergeRangeList> mergedMergeInfo; boolean isSubtree = !subtreePath.equals(reposRelPathStr); if (isSubtree) { String subtreeRelPath = subtreePath.substring(reposRelPathStr.length() + 1); subtreeSourceHistory = SVNMergeInfoUtil.appendSuffix(sourceHistory, subtreeRelPath); if (!getOperation().isFindMerged()) { subtreeHistory = SVNMergeInfoUtil.appendSuffix(history, subtreeRelPath); } } else { subtreeSourceHistory = sourceHistory; if (!getOperation().isFindMerged()) { subtreeHistory = history; } } if (!getOperation().isFindMerged()) { Map<String, SVNMergeRangeList> mergedViaHistory = SVNMergeInfoUtil.intersectMergeInfo(subtreeHistory, subtreeSourceHistory, true); subtreeMergeInfo = SVNMergeInfoUtil.mergeMergeInfos(subtreeMergeInfo, mergedViaHistory); } subtreeInheritableMergeInfo = SVNMergeInfoUtil.getInheritableMergeInfo(subtreeMergeInfo, null, -1, -1, true); subtreeNonInheritableMergeInfo = SVNMergeInfoUtil.getInheritableMergeInfo(subtreeMergeInfo, null, -1, -1, false); mergedNonInheritableMergeInfo = SVNMergeInfoUtil.intersectMergeInfo(subtreeNonInheritableMergeInfo, subtreeSourceHistory, false); if (!mergedNonInheritableMergeInfo.isEmpty()) { for (SVNMergeRangeList rl : mergedNonInheritableMergeInfo.values()) { rl.setInheritable(false); masterNonInheritableRangeList = masterNonInheritableRangeList.merge(rl.dup()); } } mergedMergeInfo = SVNMergeInfoUtil.intersectMergeInfo(subtreeInheritableMergeInfo, subtreeSourceHistory, false); SVNMergeRangeList subtreeMergeRangeList = new SVNMergeRangeList((SVNMergeRange[]) null); if (!mergedMergeInfo.isEmpty()) { for (SVNMergeRangeList rl : mergedMergeInfo.values()) { masterInheritableRangeList = masterInheritableRangeList.merge(rl.dup()); subtreeMergeRangeList = subtreeMergeRangeList.merge(rl.dup()); } } inheritableSubtreeMerges.put(subtreePath, subtreeMergeRangeList); } if (!masterInheritableRangeList.isEmpty()) { for (String path : inheritableSubtreeMerges.keySet()) { SVNMergeRangeList subtreeMergedRangeList = inheritableSubtreeMerges.get(path); // present in master, but not in subtree. SVNMergeRangeList deletedRanges = masterInheritableRangeList.diff(subtreeMergedRangeList, true); if (!deletedRanges.isEmpty()) { deletedRanges.setInheritable(false); masterNonInheritableRangeList = masterNonInheritableRangeList.merge(deletedRanges); masterInheritableRangeList = masterInheritableRangeList.remove(deletedRanges, false); } } } if (getOperation().isFindMerged()) { masterInheritableRangeList = masterInheritableRangeList.merge(masterNonInheritableRangeList); } else { SVNMergeRangeList sourceMasterRangeList = new SVNMergeRangeList((SVNMergeRange[]) null); for(SVNMergeRangeList rl : sourceHistory.values()) { sourceMasterRangeList = sourceMasterRangeList.merge(rl); } sourceMasterRangeList = sourceMasterRangeList.remove(masterNonInheritableRangeList, false); sourceMasterRangeList = sourceMasterRangeList.merge(masterNonInheritableRangeList); masterInheritableRangeList = sourceMasterRangeList.remove(masterInheritableRangeList, true); } if (masterInheritableRangeList.isEmpty()) { return getOperation().first(); } List<String> mergeSourcePaths = new ArrayList<String>(); String logTarget = null; SVNMergeRange youngestRange = masterInheritableRangeList.getRanges()[masterInheritableRangeList.getSize() - 1].dup(); SVNMergeRangeList youngestRangeList = new SVNMergeRangeList(youngestRange.getEndRevision() - 1, youngestRange.getEndRevision(), youngestRange.isInheritable()); for (String key : sourceHistory.keySet()) { SVNMergeRangeList subtreeMergedList = sourceHistory.get(key); SVNMergeRangeList intersection = youngestRangeList.intersect(subtreeMergedList, false); mergeSourcePaths.add(key); if (!intersection.isEmpty()) { logTarget = key; } } if (logTarget != null && logTarget.startsWith("/")) { logTarget = logTarget.substring(1); } SVNURL logTargetURL = logTarget != null ? SVNWCUtils.join(root[0], SVNFileUtil.createFilePath(logTarget)) : root[0]; logForMergeInfoRangeList(logTargetURL, mergeSourcePaths, getOperation().isFindMerged(), masterInheritableRangeList, oldestRevsFirst, mergeInfoCatalog, "/" + reposRelPathStr, getOperation().isDiscoverChangedPaths(), getOperation().getRevisionProperties(), getOperation()); return getOperation().first(); } @SuppressWarnings("unchecked") private void logForMergeInfoRangeList(SVNURL sourceURL, List<String> mergeSourcePaths, boolean filteringMerged, SVNMergeRangeList rangelist, boolean oldestRevsFirst, Map<String, Map<String, SVNMergeRangeList>> targetCatalog, String absReposTargetPath, boolean discoverChangedPaths, String[] revprops, ISvnObjectReceiver<SVNLogEntry> receiver) throws SVNException { if (rangelist.isEmpty()) { return; } if (targetCatalog == null) { targetCatalog = new TreeMap<String, Map<String,SVNMergeRangeList>>(); } Map<String, Map<String, SVNMergeRangeList>> adjustedCatalog = new TreeMap<String, Map<String,SVNMergeRangeList>>(); for (String relativePath : targetCatalog.keySet()) { Map<String, SVNMergeRangeList> mi = targetCatalog.get(relativePath); if (!relativePath.startsWith("/")) { relativePath = "/" + relativePath; } adjustedCatalog.put(relativePath, mi); } List<SVNMergeRange> ranges = rangelist.getRangesAsList(); Collections.sort(ranges); SVNMergeRange youngestRange = ranges.get(ranges.size() - 1); SVNMergeRange oldestRange = ranges.get(0); long youngestRev = youngestRange.getEndRevision(); long oldestRev = oldestRange.getStartRevision(); LogEntryReceiver filteringReceiver = new LogEntryReceiver(); filteringReceiver.receiver = receiver; filteringReceiver.rangelist = rangelist; filteringReceiver.isFilteringMerged = filteringMerged; filteringReceiver.targetCatalog = adjustedCatalog; filteringReceiver.mergeSourcePaths = mergeSourcePaths; filteringReceiver.reposTargertAbsPath = absReposTargetPath; SvnLog log = getOperation().getOperationFactory().createLog(); log.setSingleTarget(SvnTarget.fromURL(sourceURL, SVNRevision.create(youngestRev))); log.setDiscoverChangedPaths(true /*getOperation().isDiscoverChangedPaths()*/); log.setRevisionProperties(getOperation().getRevisionProperties()); log.setLimit(-1); log.setStopOnCopy(false); log.setUseMergeHistory(false); log.addRange(oldestRevsFirst ? SvnRevisionRange.create(SVNRevision.create(oldestRev), SVNRevision.create(youngestRev)) : SvnRevisionRange.create(SVNRevision.create(youngestRev), SVNRevision.create(oldestRev))); log.setReceiver(filteringReceiver); log.run(); } private static class LogEntryReceiver implements ISvnObjectReceiver<SVNLogEntry> { private boolean isFilteringMerged; private List<String> mergeSourcePaths; private String reposTargertAbsPath; private Map<String, Map<String, SVNMergeRangeList>> targetCatalog; private SVNMergeRangeList rangelist; private ISvnObjectReceiver<SVNLogEntry> receiver; public LogEntryReceiver() { } public void receive(SvnTarget target, SVNLogEntry logEntry) throws SVNException { if (logEntry.getRevision() == 0) { return; } SVNMergeRangeList thisRangeList = new SVNMergeRangeList(logEntry.getRevision() - 1, logEntry.getRevision(), true); SVNMergeRangeList intersection = this.rangelist.intersect(thisRangeList, false); if (intersection == null || intersection.isEmpty()) { return; } intersection = thisRangeList.intersect(rangelist, true); logEntry.setNonInheriable(intersection.isEmpty()); if ((logEntry.isNonInheritable() || !isFilteringMerged) && logEntry.getChangedPaths() != null) { boolean allSubtreesHaveThisRev = true; SVNMergeRangeList thisRevRangeList = new SVNMergeRangeList(logEntry.getRevision() - 1, logEntry.getRevision(), true); for (String changedPath : logEntry.getChangedPaths().keySet()) { String mergeSourceRelTarget = null; boolean interrupted = false; String mSourcePath = null; for (String mergeSourcePath : this.mergeSourcePaths) { mSourcePath = mergeSourcePath; mergeSourceRelTarget = mergeSourcePath.equals(changedPath) ? "" : SVNPathUtil.getPathAsChild(mergeSourcePath, changedPath); if (mergeSourceRelTarget != null) { interrupted = true; if ("".equals(mergeSourceRelTarget) && logEntry.getChangedPaths().get(changedPath).getType() != 'M') { interrupted = false; } break; } } if (!interrupted) { continue; } String targetPathAffected = SVNPathUtil.append(reposTargertAbsPath, mergeSourceRelTarget); if (!targetPathAffected.startsWith("/")) { targetPathAffected = "/" + targetPathAffected; } Map<String, SVNMergeRangeList> nearestAncestorMergeInfo = null; boolean ancestorIsSelf = false; for(String path : targetCatalog.keySet()) { if (SVNPathUtil.isAncestor(path, targetPathAffected)) { nearestAncestorMergeInfo = targetCatalog.get(path); ancestorIsSelf = path.equals(targetPathAffected); if (ancestorIsSelf) { break; } } } if (nearestAncestorMergeInfo != null && ancestorIsSelf && logEntry.getChangedPaths().get(changedPath).getType() != 'M') { // SVNMergeRangeList rlist = nearestAncestorMergeInfo.get(changedPath); SVNMergeRange youngestRange = rlist.getRanges()[rlist.getSize() - 1]; if (youngestRange.getEndRevision() > logEntry.getRevision()) { continue; } } boolean foundThisRevision = false; if (nearestAncestorMergeInfo != null) { for(String path : nearestAncestorMergeInfo.keySet()) { SVNMergeRangeList rlist = nearestAncestorMergeInfo.get(path); if (SVNPathUtil.isAncestor(mSourcePath, path)) { SVNMergeRangeList inter = rlist.intersect(thisRevRangeList, false); if (!inter.isEmpty()) { if (ancestorIsSelf) { foundThisRevision = true; break; } else { inter = rlist.intersect(thisRevRangeList, true); if (!inter.isEmpty()) { foundThisRevision = true; break; } } } } } } if (!foundThisRevision) { allSubtreesHaveThisRev = false; break; } } if (allSubtreesHaveThisRev) { if (isFilteringMerged) { logEntry.setNonInheriable(false); } else { return; } } } receiver.receive(target, logEntry); } } }