package org.tmatesoft.svn.core.internal.wc2; import java.io.File; import java.util.Collection; 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.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.SVNURL; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc.SVNMergeDriver; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.db.Structure; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields; import org.tmatesoft.svn.core.internal.wc2.ng.SvnNgRepositoryAccess; import org.tmatesoft.svn.core.internal.wc2.old.SvnOldRepositoryAccess; 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.io.SVNRepositoryFactory; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.ISvnOperationOptionsProvider; import org.tmatesoft.svn.core.wc2.SvnCopySource; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; public abstract class SvnRepositoryAccess { private SVNWCContext context; private ISvnOperationOptionsProvider operationOptionsProvider; protected SvnRepositoryAccess(ISvnOperationOptionsProvider operationOptionsProvider, SVNWCContext context) throws SVNException { this.operationOptionsProvider = operationOptionsProvider; this.context = context; } protected ISvnOperationOptionsProvider getOperationOptionsProvider() { return this.operationOptionsProvider; } protected SVNWCContext getWCContext() { return this.context; } public enum RepositoryInfo { repository, revision, url; } public enum LocationsInfo { startUrl, startRevision, endUrl, endRevision; } public enum RevisionsPair { revNumber, youngestRevision; } public abstract SvnCopySource createRemoteCopySource(SVNWCContext context, SvnCopySource localCopySource) throws SVNException; public abstract Structure<RepositoryInfo> createRepositoryFor(SvnTarget target, SVNRevision revision, SVNRevision pegRevision, File baseDirectory) throws SVNException; public abstract Structure<RevisionsPair> getRevisionNumber(SVNRepository repository, SvnTarget path, SVNRevision revision, Structure<RevisionsPair> youngestRevision) throws SVNException; public enum UrlInfo { url, pegRevision, dropRepsitory; } public abstract Structure<UrlInfo> getURLFromPath(SvnTarget path, SVNRevision revision, SVNRepository repository) throws SVNException; protected SVNRevision[] resolveRevisions(SVNRevision pegRevision, SVNRevision revision, boolean isURL, boolean noticeLocalModifications) { if (!pegRevision.isValid()) { if (isURL) { pegRevision = SVNRevision.HEAD; } else { if (noticeLocalModifications) { pegRevision = SVNRevision.WORKING; } else { pegRevision = SVNRevision.BASE; } } } if (!revision.isValid()) { revision = pegRevision; } return new SVNRevision[] { pegRevision, revision }; } public SVNRepository createRepository(SVNURL url, String expectedUuid, boolean mayReuse) throws SVNException { SVNRepository repository = null; if (getOperationOptionsProvider().getRepositoryPool() == null) { repository = SVNRepositoryFactory.create(url, null); repository.setAuthenticationManager(getOperationOptionsProvider().getAuthenticationManager()); } else { repository = getOperationOptionsProvider().getRepositoryPool().createRepository(url, mayReuse); } if (expectedUuid != null) { String reposUUID = repository.getRepositoryUUID(true); if (!expectedUuid.equals(reposUUID)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_UUID_MISMATCH, "Repository UUID ''{0}'' doesn''t match expected UUID ''{1}''", new Object[] { reposUUID, expectedUuid }); SVNErrorManager.error(err, SVNLogType.WC); } } repository.setCanceller(getOperationOptionsProvider().getCanceller()); return repository; } public Structure<LocationsInfo> getLocations(SVNRepository repository, SvnTarget path, SVNRevision revision, SVNRevision start, SVNRevision end) throws SVNException { if (!revision.isValid() || !start.isValid()) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION), SVNLogType.DEFAULT); } long pegRevisionNumber = -1; long startRevisionNumber; long endRevisionNumber; SVNURL url = null; if (path.isFile()) { if (revision == SVNRevision.WORKING && getWCContext() != null) { Structure<StructureFields.NodeOriginInfo> nodeOrigin = getWCContext().getNodeOrigin(path.getFile(), false, StructureFields.NodeOriginInfo.isCopy, StructureFields.NodeOriginInfo.revision, StructureFields.NodeOriginInfo.reposRelpath, StructureFields.NodeOriginInfo.reposRootUrl); boolean isCopy = nodeOrigin.is(StructureFields.NodeOriginInfo.isCopy); long pegRevNum = nodeOrigin.lng(StructureFields.NodeOriginInfo.revision); File reposRelPath = nodeOrigin.get(StructureFields.NodeOriginInfo.reposRelpath); SVNURL reposRootUrl = nodeOrigin.get(StructureFields.NodeOriginInfo.reposRootUrl); if (reposRelPath != null) { url = reposRootUrl.appendPath(SVNFileUtil.getFilePath(reposRelPath), false); } else { url = null; } if (url != null && isCopy && repository != null) { SVNURL sessionUrl = repository.getLocation(); if (!sessionUrl.equals(url)) { repository = null; } } } else { url = null; } if (url == null) { Structure<UrlInfo> urlInfo = getURLFromPath(path, revision, repository); if (urlInfo.hasValue(UrlInfo.dropRepsitory) && urlInfo.is(UrlInfo.dropRepsitory)) { repository = null; } url = urlInfo.<SVNURL>get(UrlInfo.url); if (urlInfo.hasValue(UrlInfo.pegRevision)) { pegRevisionNumber = urlInfo.lng(UrlInfo.pegRevision); } urlInfo.release(); } if (url == null) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", path.getFile()); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } else { url = path.getURL(); } if (repository == null) { repository = createRepository(url, null, true); } Structure<RevisionsPair> pair = null; if (pegRevisionNumber < 0) { pair = getRevisionNumber(repository, path, revision, pair); pegRevisionNumber = pair.lng(RevisionsPair.revNumber); } pair = getRevisionNumber(repository, path, start, pair); startRevisionNumber = pair.lng(RevisionsPair.revNumber); if (end == SVNRevision.UNDEFINED) { endRevisionNumber = startRevisionNumber; } else { pair = getRevisionNumber(repository, path, end, pair); endRevisionNumber = pair.lng(RevisionsPair.revNumber); } pair.release(); Structure<LocationsInfo> result = Structure.obtain(LocationsInfo.class); result.set(LocationsInfo.startRevision, startRevisionNumber); if (end != SVNRevision.UNDEFINED) { result.set(LocationsInfo.startRevision, endRevisionNumber); } if (startRevisionNumber == pegRevisionNumber && (endRevisionNumber == pegRevisionNumber || !SVNRevision.isValidRevisionNumber(endRevisionNumber))) { result.set(LocationsInfo.startUrl, url); result.set(LocationsInfo.endUrl, url); return result; } SVNURL repositoryRootURL = repository.getRepositoryRoot(true); long[] revisionsRange = startRevisionNumber == endRevisionNumber ? new long[] {startRevisionNumber} : new long[] {startRevisionNumber, endRevisionNumber}; Map<?,?> locations = repository.getLocations("", (Map<?,?>) null, pegRevisionNumber, revisionsRange); SVNLocationEntry startPath = (SVNLocationEntry) locations.get(new Long(startRevisionNumber)); SVNLocationEntry endPath = (SVNLocationEntry) locations.get(new Long(endRevisionNumber)); if (startPath == null) { Object source = path != null ? (Object) path : (Object) url; SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "Unable to find repository location for ''{0}'' in revision ''{1}''", new Object[] { source, new Long(startRevisionNumber) }); SVNErrorManager.error(err, SVNLogType.WC); } if (endPath == null) { Object source = path != null ? (Object) path : (Object) url; SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "The location for ''{0}'' for revision {1} does not exist in the " + "repository or refers to an unrelated object", new Object[] { source, new Long(endRevisionNumber) }); SVNErrorManager.error(err, SVNLogType.WC); } result.set(LocationsInfo.startUrl, repositoryRootURL.appendPath(startPath.getPath(), false)); if (end.isValid()) { result.set(LocationsInfo.endUrl, repositoryRootURL.appendPath(endPath.getPath(), false)); } return result; } public Map<String, SVNMergeRangeList> getReposMergeInfo(SVNRepository repository, String path, long revision, SVNMergeInfoInheritance inheritance, boolean squelchIncapable) throws SVNException { Map<String, SVNMergeInfo> reposMergeInfo = null; try { reposMergeInfo = repository.getMergeInfo(new String[] {path}, revision, inheritance, false); } catch (SVNException svne) { if (!squelchIncapable || svne.getErrorMessage().getErrorCode() != SVNErrorCode.UNSUPPORTED_FEATURE) { throw svne; } } String rootRelativePath = getPathRelativeToRoot(repository.getLocation(), repository.getRepositoryRoot(false), repository); Map<String, SVNMergeRangeList> targetMergeInfo = null; if (reposMergeInfo != null) { SVNMergeInfo mergeInfo = (SVNMergeInfo) reposMergeInfo.get(rootRelativePath); if (mergeInfo != null) { targetMergeInfo = mergeInfo.getMergeSourcesToMergeLists(); } } return targetMergeInfo; } protected String getPathRelativeToRoot(SVNURL url, SVNURL reposRootURL, SVNRepository repos) throws SVNException { if (reposRootURL == null) { reposRootURL = repos.getRepositoryRoot(true); } String reposRootPath = reposRootURL.getPath(); String absPath = url.getPath(); if (!absPath.startsWith(reposRootPath)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "URL ''{0}'' is not a child of repository root URL ''{1}''", new Object[] { url, reposRootURL }); SVNErrorManager.error(err, SVNLogType.WC); } absPath = absPath.substring(reposRootPath.length()); if (!absPath.startsWith("/")) { absPath = "/" + absPath; } return absPath; } public String getPathRelativeToSession(SVNURL url, SVNURL sessionURL, SVNRepository repos) { if (sessionURL == null) { sessionURL = repos.getLocation(); } String reposPath = sessionURL.getPath(); String absPath = url.getPath(); if (!absPath.startsWith(reposPath + "/") && !absPath.equals(reposPath)) { return null; } absPath = absPath.substring(reposPath.length()); if (absPath.startsWith("/")) { absPath = absPath.substring(1); } return absPath; } public SVNLocationSegment getYoungestCommonAncestor(SVNURL url1, long rev1, SVNURL url2, long rev2) throws SVNException { boolean[] hasZero1 = new boolean[1]; boolean[] hasZero2 = new boolean[1]; Map<String, SVNMergeRangeList> history1 = getHistoryAsMergeInfo(url1, SVNRevision.create(rev1), -1, -1, hasZero1, null); Map<String, SVNMergeRangeList> history2 = getHistoryAsMergeInfo(url2, SVNRevision.create(rev2), -1, -1, hasZero2, null); long ycRevision = -1; String ycPath = null; for (Iterator<String> paths = history1.keySet().iterator(); paths.hasNext();) { String path = paths.next(); SVNMergeRangeList ranges1 = history1.get(path); SVNMergeRangeList ranges2 = history2.get(path); if (ranges2 != null) { SVNMergeRangeList intersection = ranges1.intersect(ranges2, true); if (intersection != null && !intersection.isEmpty()) { SVNMergeRange ycRange = intersection.getRanges()[intersection.getSize() - 1]; if (ycRevision < 0 || ycRange.getEndRevision() > ycRevision) { ycRevision = ycRange.getEndRevision(); ycPath = path.substring(1); } } } } if (ycPath == null && hasZero1[0] && hasZero2[0]) { ycPath = "/"; ycRevision = 0; } return new SVNLocationSegment(ycRevision, ycRevision, ycPath); } private Map<String, SVNMergeRangeList> getHistoryAsMergeInfo(SVNURL url, SVNRevision pegRevision, long rangeYoungest, long rangeOldest, boolean[] hasZero, SVNRepository repos) throws SVNException { long[] pegRevNum = new long[1]; Structure<RevisionsPair> pair = getRevisionNumber(repos, SvnTarget.fromURL(url), pegRevision, null); pegRevNum[0] = pair.lng(RevisionsPair.revNumber); pair.release(); boolean closeSession = false; try { if (repos == null) { repos = createRepository(url, null, false); closeSession = true; } if (!SVNRevision.isValidRevisionNumber(rangeYoungest)) { rangeYoungest = pegRevNum[0]; } if (!SVNRevision.isValidRevisionNumber(rangeOldest)) { rangeOldest = 0; } List<SVNLocationSegment> segments = repos.getLocationSegments("", pegRevNum[0], rangeYoungest, rangeOldest); if (!segments.isEmpty() && hasZero != null && hasZero.length > 0) { SVNLocationSegment oldest = segments.get(0); hasZero[0] = oldest.getStartRevision() == 0; } return getMergeInfoFromSegments(segments); } finally { if (closeSession) { repos.closeSession(); } } } public static Map<String, SVNMergeRangeList> getMergeInfoFromSegments(Collection<SVNLocationSegment> segments) { Map<String, Collection<SVNMergeRange>> mergeInfo = new TreeMap<String, Collection<SVNMergeRange>>(); for (Iterator<SVNLocationSegment> segmentsIter = segments.iterator(); segmentsIter.hasNext();) { SVNLocationSegment segment = (SVNLocationSegment) segmentsIter.next(); if (segment.getPath() == null) { continue; } String sourcePath = segment.getPath(); Collection<SVNMergeRange> pathRanges = mergeInfo.get(sourcePath); if (pathRanges == null) { pathRanges = new LinkedList<SVNMergeRange>(); mergeInfo.put(sourcePath, pathRanges); } SVNMergeRange range = new SVNMergeRange(Math.max(segment.getStartRevision() - 1, 0), segment.getEndRevision(), true); pathRanges.add(range); } Map<String, SVNMergeRangeList> result = new TreeMap<String, SVNMergeRangeList>(); for (Iterator<String> paths = mergeInfo.keySet().iterator(); paths.hasNext();) { String path = (String) paths.next(); Collection<SVNMergeRange> pathRanges = mergeInfo.get(path); result.put(path, SVNMergeRangeList.fromCollection(pathRanges)); } return result; } public SVNLocationEntry getCopySource(SvnTarget target, SVNRevision revision) throws SVNException { Structure<RepositoryInfo> repositoryInfo = createRepositoryFor(target, revision, revision, null); SVNRepository repository = repositoryInfo.get(RepositoryInfo.repository); long atRev = repositoryInfo.lng(RepositoryInfo.revision); repositoryInfo.release(); final Object[] copyFrom = new Object[3]; try { repository.getLocationSegments("", atRev, atRev, -1, new ISVNLocationSegmentHandler() { public void handleLocationSegment(SVNLocationSegment locationSegment) throws SVNException { // skip first. if (copyFrom[0] == null) { copyFrom[0] = Boolean.TRUE; } else if (copyFrom[1] != null) { return; } else if (locationSegment.getPath() != null) { copyFrom[1] = locationSegment.getPath(); copyFrom[2] = locationSegment.getEndRevision(); } } }); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND || e.getErrorMessage().getErrorCode() == SVNErrorCode.RA_DAV_REQUEST_FAILED) { return new SVNLocationEntry(-1, null); } else { throw e; } } if (copyFrom[1] != null) { return new SVNLocationEntry((Long) copyFrom[2], (String) copyFrom[1]); } return null; } public Map<String, SVNMergeRangeList> getHistoryAsMergeInfo(SVNRepository repos, SvnTarget target, long youngest, long oldest) throws SVNException { long pegRevnum = -1; if (repos == null) { Structure<RepositoryInfo> reposInfo = createRepositoryFor(target, SVNRevision.UNDEFINED, target.getResolvedPegRevision(), null); pegRevnum = reposInfo.lng(RepositoryInfo.revision); repos = reposInfo.get(RepositoryInfo.repository); reposInfo.release(); } else { if (target.getPegRevision() == SVNRevision.HEAD || target.getPegRevision() == SVNRevision.UNDEFINED) { pegRevnum = repos.getLatestRevision(); } else if (target.getPegRevision().getNumber() >= 0) { pegRevnum = target.getPegRevision().getNumber(); } else if (target.getPegRevision().getDate() != null) { pegRevnum = repos.getDatedRevision(target.getPegRevision().getDate()); } else { if (target.isURL()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_VERSIONED_PATH_REQUIRED); SVNErrorManager.error(err, SVNLogType.WC); } Structure<RevisionsPair> pair = getRevisionNumber(repos, target, target.getPegRevision(), null); pegRevnum = pair.lng(RevisionsPair.revNumber); pair.release(); } } if (youngest < 0) { youngest = pegRevnum; } if (oldest < 0) { oldest = 0; } List<SVNLocationSegment> segments = repos.getLocationSegments("", pegRevnum, youngest, oldest); return SVNMergeDriver.getMergeInfoFromSegments(segments); } }