package org.tmatesoft.svn.core.internal.wc2.ng; import java.io.File; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.tmatesoft.svn.core.ISVNLogEntryHandler; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLogEntry; 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.SVNProperty; 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.util.SVNURLUtil; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileType; 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.SvnRepositoryAccess.LocationsInfo; import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.RepositoryInfo; import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; import org.tmatesoft.svn.core.io.SVNLocationSegment; 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.SvnGetProperties; import org.tmatesoft.svn.core.wc2.SvnMerge; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; public class SvnNgMergeReintegrate extends SvnNgOperationRunner<Void, SvnMerge>{ @Override public boolean isApplicable(SvnMerge operation, SvnWcGeneration wcGeneration) throws SVNException { if (super.isApplicable(operation, wcGeneration)) { return operation.isReintegrate(); } return false; } @Override protected Void run(SVNWCContext context) throws SVNException { File lockPath = getLockPath(getFirstTarget()); if (getOperation().isDryRun()) { merge(context, getOperation().getSource(), getFirstTarget(), getOperation().isDryRun()); } else { try { lockPath = getWcContext().acquireWriteLock(lockPath, false, true); merge(context, getOperation().getSource(), getFirstTarget(), getOperation().isDryRun()); } finally { getWcContext().releaseWriteLock(lockPath); sleepForTimestamp(); } } return null; } private File getLockPath(File firstTarget) throws SVNException { SVNNodeKind kind = getWcContext().readKind(firstTarget, false); if (kind == SVNNodeKind.DIR) { return firstTarget; } else { return SVNFileUtil.getParentFile(firstTarget); } } private void merge(SVNWCContext context, SvnTarget mergeSource, File mergeTarget, boolean dryRun) throws SVNException { SVNFileType targetKind = SVNFileType.getType(mergeTarget); if (targetKind == SVNFileType.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "Path ''{0}'' does not exist", mergeTarget); SVNErrorManager.error(err, SVNLogType.WC); } SVNURL url2 = getRepositoryAccess().getTargetURL(mergeSource); if (url2 == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", mergeTarget); SVNErrorManager.error(err, SVNLogType.WC); } SVNURL wcReposRoot = context.getNodeReposInfo(mergeTarget).reposRootUrl; Structure<RepositoryInfo> sourceReposInfo = getRepositoryAccess().createRepositoryFor(mergeSource, mergeSource.getPegRevision(), mergeSource.getPegRevision(), null); SVNURL sourceReposRoot = ((SVNRepository) sourceReposInfo.get(RepositoryInfo.repository)).getRepositoryRoot(true); if (!wcReposRoot.equals(sourceReposRoot)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "''{0}'' must be from the same repositor as ''{1}''", mergeSource.getURL(), mergeTarget); SVNErrorManager.error(err, SVNLogType.WC); } SvnNgMergeDriver mergeDriver = new SvnNgMergeDriver(getWcContext(), getOperation(), getRepositoryAccess(), getOperation().getMergeOptions()); mergeDriver.ensureWcIsSuitableForMerge(mergeTarget, false, false, false); long targetBaseRev = context.getNodeBaseRev(mergeTarget); long rev1 = targetBaseRev; File sourceReposRelPath = new File(SVNURLUtil.getRelativeURL(wcReposRoot, url2, false)); File targetReposRelPath = context.getNodeReposRelPath(mergeTarget); if ("".equals(sourceReposRelPath.getPath()) || "".equals(targetReposRelPath.getPath())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_NOT_READY_TO_MERGE, "Neither reintegrate source nor target can be the root of repository"); SVNErrorManager.error(err, SVNLogType.WC); } final Map<File, String> explicitMergeInfo = new HashMap<File, String>(); SvnGetProperties pg = getOperation().getOperationFactory().createGetProperties(); pg.setDepth(SVNDepth.INFINITY); pg.setSingleTarget(SvnTarget.fromFile(mergeTarget)); pg.setReceiver(new ISvnObjectReceiver<SVNProperties>() { public void receive(SvnTarget target, SVNProperties props) throws SVNException { final String value = props.getStringValue(SVNProperty.MERGE_INFO); if (value != null) { explicitMergeInfo.put(target.getFile(), value); } } }); pg.run(); if (!explicitMergeInfo.isEmpty()) { final Map<File, File> externals = context.getDb().getExternalsDefinedBelow(mergeTarget); for (Iterator<File> wcPaths = explicitMergeInfo.keySet().iterator(); wcPaths.hasNext();) { final File wcPath = wcPaths.next(); if (externals.containsKey(wcPath)) { wcPaths.remove(); } } } sourceReposInfo = getRepositoryAccess().createRepositoryFor(SvnTarget.fromURL(url2), SVNRevision.UNDEFINED, mergeSource.getPegRevision(), null); SVNRepository sourceRepository = sourceReposInfo.get(RepositoryInfo.repository); long rev2 = sourceReposInfo.lng(RepositoryInfo.revision); url2 = sourceReposInfo.get(RepositoryInfo.url); sourceReposInfo.release(); SVNURL targetUrl = context.getNodeUrl(mergeTarget); SVNRepository targetRepository = getRepositoryAccess().createRepository(targetUrl, null, false); // try { Map<File, Map<String, SVNMergeRangeList>> mergedToSourceCatalog = new HashMap<File, Map<String,SVNMergeRangeList>>(); Map<File, Map<String, SVNMergeRangeList>> unmergedToSourceCatalog = new HashMap<File, Map<String,SVNMergeRangeList>>(); SvnTarget url1 = calculateLeftHandSide(context, mergedToSourceCatalog, unmergedToSourceCatalog, mergeTarget, targetReposRelPath, explicitMergeInfo, targetBaseRev, sourceReposRelPath, sourceReposRoot, wcReposRoot, rev2, sourceRepository, targetRepository); if (url1 == null) { return; } if (!url1.getURL().equals(targetUrl)) { targetRepository.setLocation(url1.getURL(), false); } rev1 = url1.getPegRevision().getNumber(); SVNLocationSegment yc = getRepositoryAccess().getYoungestCommonAncestor(url2, rev2, url1.getURL(), rev1); if (yc == null || !(yc.getPath() != null && yc.getStartRevision() >= 0)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_NOT_READY_TO_MERGE, "''{0}''@''{1}'' must be ancestrally related to ''{2}''@''{3}''", url1, new Long(rev1), url2, new Long(rev2)); SVNErrorManager.error(err, SVNLogType.WC); } if (rev1 > yc.getStartRevision()) { Map<File, Map<String, SVNMergeRangeList>> finalUnmergedCatalog = new HashMap<File, Map<String,SVNMergeRangeList>>(); String ycPath = yc.getPath(); if (ycPath.startsWith("/")) { ycPath = ycPath.substring(1); } findUnsyncedRanges(sourceReposRelPath, new File(ycPath), unmergedToSourceCatalog, mergedToSourceCatalog, finalUnmergedCatalog, targetRepository); if (!finalUnmergedCatalog.isEmpty()) { String catalog = SVNMergeInfoUtil.formatMergeInfoCatalogToString2(finalUnmergedCatalog, " ", " Missing ranges: "); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_NOT_READY_TO_MERGE, "Reintegrate can only be used if revisions {0} through {1} were " + "previously merged from {2} to the reintegrate source, but this is " + "not the case:\n{3}", new Object[] {new Long(yc.getStartRevision() + 1), new Long(rev2), targetUrl, catalog}); SVNErrorManager.error(err, SVNLogType.WC); } } mergeDriver.mergeCousinsAndSupplementMergeInfo(mergeTarget, targetRepository, sourceRepository, url1.getURL(), rev1, url2, rev2, yc.getStartRevision(), sourceReposRoot, wcReposRoot, SVNDepth.INFINITY, false, false, false, false, dryRun); } finally { targetRepository.closeSession(); } } private void findUnsyncedRanges( final File sourceReposRelPath, final File targetReposRelPath, Map<File, Map<String, SVNMergeRangeList>> unmergedToSourceCatalog, final Map<File, Map<String, SVNMergeRangeList>> mergedToSourceCatalog, final Map<File, Map<String, SVNMergeRangeList>> finalUnmergedCatalog, SVNRepository repos) throws SVNException { SVNMergeRangeList potentiallyUnmergedRanges = null; if (unmergedToSourceCatalog != null) { potentiallyUnmergedRanges = new SVNMergeRangeList(new SVNMergeRange[0]); for (File cpath : unmergedToSourceCatalog.keySet()) { Map<String, SVNMergeRangeList> mi = unmergedToSourceCatalog.get(cpath); for (SVNMergeRangeList mrl : mi.values()) { potentiallyUnmergedRanges = potentiallyUnmergedRanges.merge(mrl); } } } if (potentiallyUnmergedRanges != null && !potentiallyUnmergedRanges.isEmpty()) { long youngest = potentiallyUnmergedRanges.getRanges()[0].getStartRevision() + 1; long oldest = potentiallyUnmergedRanges.getRanges()[potentiallyUnmergedRanges.getSize() - 1].getEndRevision(); repos.log(new String[] {""}, youngest, oldest, true, false, -1, false, null, new ISVNLogEntryHandler() { public void handleLogEntry(SVNLogEntry logEntry) throws SVNException { for (String changedPath : logEntry.getChangedPaths().keySet()) { if (changedPath.startsWith("/")) { changedPath = changedPath.substring(1); } String relPath = SVNWCUtils.isChild(SVNFileUtil.getFilePath(targetReposRelPath), changedPath); if (relPath == null) { continue; } File sourceRelpath = SVNFileUtil.createFilePath(sourceReposRelPath, relPath); String mergeInfoForPath = "/" + changedPath + ":" + logEntry.getRevision(); Map<String, SVNMergeRangeList> mi = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mergeInfoForPath), null); File[] subtreeMissing = new File[1]; boolean inCatalog = isMergeinfoInCatalog(sourceRelpath, subtreeMissing, mi, logEntry.getRevision(), mergedToSourceCatalog); if (!inCatalog) { File missingPath = null; File subtreeMissingInThisRev = subtreeMissing[0]; if (subtreeMissingInThisRev == null) { subtreeMissingInThisRev = sourceReposRelPath; } if (subtreeMissingInThisRev != null && !subtreeMissingInThisRev.equals(sourceRelpath)) { missingPath = SVNWCUtils.skipAncestor(subtreeMissingInThisRev, sourceRelpath); missingPath = new File(changedPath.substring(0, changedPath.length() - missingPath.getPath().length())); } else { missingPath = new File(changedPath); } Map<String, SVNMergeRangeList> mi2 = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer("/" + SVNFileUtil.getFilePath(missingPath) + ":" + logEntry.getRevision()), null); Map<String, SVNMergeRangeList> existing = finalUnmergedCatalog.get(missingPath); if (existing != null) { existing = SVNMergeInfoUtil.mergeMergeInfos(existing, mi2); } else { existing = mi2; } finalUnmergedCatalog.put(subtreeMissingInThisRev, existing); } } } }); } } private boolean isMergeinfoInCatalog(File sourceRelpath, File[] catPath, Map<String, SVNMergeRangeList> mergeinfo, long revision, Map<File, Map<String, SVNMergeRangeList>> catalog) throws SVNException { if (mergeinfo != null && catalog != null && !catalog.isEmpty()) { File path = sourceRelpath; String walkPath = null; Map<String, SVNMergeRangeList> miInCatalog = null; while(true) { miInCatalog = catalog.get(path); if (miInCatalog != null) { if (catPath != null) { catPath[0] = path; } break; } else { walkPath = walkPath != null ? SVNPathUtil.append(SVNFileUtil.getFileName(path), walkPath) : SVNFileUtil.getFileName(path); path = path.getParentFile(); if (path == null) { break; } } } if (miInCatalog != null) { if (walkPath != null) { miInCatalog = SVNMergeInfoUtil.adjustMergeInfoSourcePaths(null, walkPath, miInCatalog); } miInCatalog = SVNMergeInfoUtil.intersectMergeInfo(miInCatalog, mergeinfo, true); return SVNMergeInfoUtil.mergeInfoEquals(mergeinfo, miInCatalog, true); } } return false; } private SvnTarget calculateLeftHandSide(SVNWCContext context, Map<File, Map<String, SVNMergeRangeList>> mergedToSourceCatalog, Map<File, Map<String, SVNMergeRangeList>> unmergedToSourceCatalog, File targetAbsPath, File targetReposRelPath, Map<File, String> subtreesWithMergeInfo, long targetRev, File sourceReposRelPath, SVNURL sourceReposRoot, SVNURL targetReposRoot, long sourceRev, SVNRepository sourceRepository, SVNRepository targetRepository) throws SVNException { if (!subtreesWithMergeInfo.containsKey(targetAbsPath)) { subtreesWithMergeInfo.put(targetAbsPath, ""); } final Map<File, List<SVNLocationSegment>> segmentsMap = new HashMap<File, List<SVNLocationSegment>>(); for (File path : subtreesWithMergeInfo.keySet()) { String miValue = subtreesWithMergeInfo.get(path); try { SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(miValue), null); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.MERGE_INFO_PARSE_ERROR) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING, "Invalid mergeinfo detected on ''{0}'', reintegrate merge not possible", path); SVNErrorManager.error(err, SVNLogType.WC); } throw e; } File pathReposRelPath = context.getNodeReposRelPath(path); File pathSessionRelPath = SVNWCUtils.skipAncestor(targetReposRelPath, pathReposRelPath); if (pathSessionRelPath == null && pathReposRelPath.equals(targetReposRelPath)) { pathSessionRelPath = new File(""); } List<SVNLocationSegment> segments = targetRepository.getLocationSegments(SVNFileUtil.getFilePath(pathSessionRelPath), targetRev, targetRev, -1); segmentsMap.put(pathReposRelPath, segments); } SVNURL sourceUrl = SVNWCUtils.join(sourceReposRoot, sourceReposRelPath); SVNURL targetUrl = SVNWCUtils.join(targetReposRoot, targetReposRelPath); SVNLocationSegment yc = getRepositoryAccess().getYoungestCommonAncestor(sourceUrl, sourceRev, targetUrl, targetRev); if (!(yc != null && yc.getPath() != null && yc.getStartRevision() >= 0)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_NOT_READY_TO_MERGE, "''{0}''@''{1}'' must be ancestrally related to ''{2}''@''{3}''", sourceUrl, new Long(sourceRev), targetUrl, new Long(targetRev)); SVNErrorManager.error(err, SVNLogType.WC); } if (sourceRev == yc.getStartRevision()) { return null; } Map<File, Map<String, SVNMergeRangeList>> mergeInfoCatalog = SvnNgMergeinfoUtil.convertToCatalog2(sourceRepository.getMergeInfo(new String[] {""}, sourceRev, SVNMergeInfoInheritance.INHERITED, true)); if (mergedToSourceCatalog != null) { mergedToSourceCatalog.putAll(mergeInfoCatalog); } UnmergedMergeInfo unmergedMergeInfo = findUnmergedMergeInfo(yc.getStartRevision(), mergeInfoCatalog, segmentsMap, sourceReposRelPath, targetReposRelPath, targetRev, sourceRev, sourceRepository, targetRepository); unmergedMergeInfo.catalog = SVNMergeInfoUtil.elideMergeInfoCatalog(unmergedMergeInfo.catalog); if (unmergedToSourceCatalog != null && unmergedMergeInfo.catalog != null) { for (String path : unmergedMergeInfo.catalog.keySet()) { Map<String, SVNMergeRangeList> mi = unmergedMergeInfo.catalog.get(path); if (path.startsWith("/")) { path = path.substring(1); } path = path.replace(File.separatorChar, '/'); unmergedToSourceCatalog.put(new File(path), mi); } } if (unmergedMergeInfo.neverSynced) { return SvnTarget.fromURL(sourceReposRoot.appendPath(yc.getPath(), false), SVNRevision.create(yc.getStartRevision())); } else { Structure<LocationsInfo> locations = getRepositoryAccess().getLocations(targetRepository, SvnTarget.fromURL(targetUrl), SVNRevision.create(targetRev), SVNRevision.create(unmergedMergeInfo.youngestMergedRevision), SVNRevision.UNDEFINED); SVNURL youngestUrl = locations.get(LocationsInfo.startUrl); locations.release(); return SvnTarget.fromURL(youngestUrl, SVNRevision.create(unmergedMergeInfo.youngestMergedRevision)); } } private UnmergedMergeInfo findUnmergedMergeInfo(long ycAncestorRev, Map<File, Map<String, SVNMergeRangeList>> sourceCatalog, Map<File, List<SVNLocationSegment>> targetSegments, File sourceReposRelPath, File targetReposRelPath, long targetRev, long sourceRev, SVNRepository sourceRepos, SVNRepository targetRepos) throws SVNException { UnmergedMergeInfo result = new UnmergedMergeInfo(); result.neverSynced = true; Map<String, Map<String, SVNMergeRangeList>> newCatalog = new TreeMap<String, Map<String,SVNMergeRangeList>>(); for (File path : targetSegments.keySet()) { List<SVNLocationSegment> segments = targetSegments.get(path); File sourcePathRelToSession = SVNWCUtils.skipAncestor(targetReposRelPath, path); if (sourcePathRelToSession == null && targetReposRelPath.equals(path)) { sourcePathRelToSession = new File(""); } File sourcePath = SVNFileUtil.createFilePath(sourceReposRelPath, sourcePathRelToSession); Map<String, SVNMergeRangeList> targetHistoryAsMergeInfo = SvnRepositoryAccess.getMergeInfoFromSegments(segments); targetHistoryAsMergeInfo = SVNMergeInfoUtil.filterMergeInfoByRanges(targetHistoryAsMergeInfo, sourceRev, ycAncestorRev); Map<String, SVNMergeRangeList> sourceMergeInfo = sourceCatalog.get(sourcePath); if (sourceMergeInfo != null) { sourceCatalog.remove(SVNFileUtil.getFileDir(sourcePath)); Map<String, SVNMergeRangeList> explicitIntersection = SVNMergeInfoUtil.intersectMergeInfo(sourceMergeInfo, targetHistoryAsMergeInfo, true); if (explicitIntersection != null && !explicitIntersection.isEmpty()) { result.neverSynced = false; long[] endPoints = SVNMergeInfoUtil.getRangeEndPoints(explicitIntersection); if (result.youngestMergedRevision < 0 || endPoints[0] > result.youngestMergedRevision) { result.youngestMergedRevision = endPoints[0]; } } } else { SVNNodeKind kind = sourceRepos.checkPath(SVNFileUtil.getFilePath(sourcePathRelToSession), sourceRev); if (kind == SVNNodeKind.NONE) { continue; } Map<File, Map<String, SVNMergeRangeList>> subtreeCatalog = SvnNgMergeinfoUtil.convertToCatalog2(sourceRepos.getMergeInfo(new String[] {SVNFileUtil.getFilePath(sourcePathRelToSession)}, sourceRev, SVNMergeInfoInheritance.INHERITED, false)); sourceMergeInfo = subtreeCatalog.get(sourcePathRelToSession); if (sourceMergeInfo == null) { sourceMergeInfo = new HashMap<String, SVNMergeRangeList>(); } } segments = sourceRepos.getLocationSegments(SVNFileUtil.getFilePath(sourcePathRelToSession), sourceRev, sourceRev, -1); Map<String, SVNMergeRangeList> sourceHistroryAsMergeInfo = SvnRepositoryAccess.getMergeInfoFromSegments(segments); sourceMergeInfo = SVNMergeInfoUtil.mergeMergeInfos(sourceMergeInfo, sourceHistroryAsMergeInfo); Map<String, SVNMergeRangeList> commonMergeInfo = SVNMergeInfoUtil.intersectMergeInfo(sourceMergeInfo, targetHistoryAsMergeInfo, true); Map<String, SVNMergeRangeList> filteredMergeInfo = SVNMergeInfoUtil.removeMergeInfo(commonMergeInfo, targetHistoryAsMergeInfo, true); newCatalog.put(SVNFileUtil.getFilePath(sourcePath), filteredMergeInfo); } if (!sourceCatalog.isEmpty()) { for(File path : sourceCatalog.keySet()) { File sourcePathRelToSession = sourceReposRelPath.equals(path) ? new File("") : SVNWCUtils.skipAncestor(sourceReposRelPath, path); File targetPath = sourceReposRelPath.equals(path) ? new File("") : SVNWCUtils.skipAncestor(sourceReposRelPath, path); List<SVNLocationSegment> segments = null; Map<String, SVNMergeRangeList> sourceMergeInfo = sourceCatalog.get(path); try { segments = targetRepos.getLocationSegments(SVNFileUtil.getFilePath(targetPath), targetRev, targetRev, -1); } catch (SVNException e) { SVNErrorCode ec = e.getErrorMessage().getErrorCode(); if (ec == SVNErrorCode.FS_NOT_FOUND || ec == SVNErrorCode.RA_DAV_REQUEST_FAILED) { continue; } throw e; } Map<String, SVNMergeRangeList> targetHistoryAsMergeInfo = SvnRepositoryAccess.getMergeInfoFromSegments(segments); Map<String, SVNMergeRangeList> explicitIntersection = SVNMergeInfoUtil.intersectMergeInfo(sourceMergeInfo, targetHistoryAsMergeInfo, true); if (explicitIntersection != null && !explicitIntersection.isEmpty()) { result.neverSynced = false; long[] endPoints = SVNMergeInfoUtil.getRangeEndPoints(explicitIntersection); if (result.youngestMergedRevision < 0 || endPoints[0] > result.youngestMergedRevision) { result.youngestMergedRevision = endPoints[0]; } } segments = sourceRepos.getLocationSegments(SVNFileUtil.getFilePath(sourcePathRelToSession), targetRev, targetRev, -1); Map<String, SVNMergeRangeList> sourceHistoryAsMergeInfo = SvnRepositoryAccess.getMergeInfoFromSegments(segments); sourceMergeInfo = SVNMergeInfoUtil.mergeMergeInfos(sourceMergeInfo, sourceHistoryAsMergeInfo); Map<String, SVNMergeRangeList> commonMergeInfo = SVNMergeInfoUtil.intersectMergeInfo(sourceMergeInfo, targetHistoryAsMergeInfo, true); Map<String, SVNMergeRangeList> filteredMergeInfo = SVNMergeInfoUtil.removeMergeInfo(commonMergeInfo, targetHistoryAsMergeInfo, true); if (!filteredMergeInfo.isEmpty()) { newCatalog.put(SVNFileUtil.getFilePath(path), filteredMergeInfo); } } } if (result.youngestMergedRevision >= 0) { newCatalog = SVNMergeInfoUtil.filterCatalogByRanges(newCatalog, result.youngestMergedRevision, 0); } result.catalog = newCatalog; return result; } private static class UnmergedMergeInfo { private Map<String, Map<String, SVNMergeRangeList>> catalog; private boolean neverSynced; private long youngestMergedRevision; } }