package org.tmatesoft.svn.core.internal.wc2.ng; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNURL; 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.SvnConflictReport; import org.tmatesoft.svn.core.internal.wc17.db.Structure; import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.RevisionsPair; import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; import org.tmatesoft.svn.core.internal.wc2.ng.SvnNgMergeDriver.MergeSource; 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.wc.SVNRevisionRange; import org.tmatesoft.svn.core.wc2.SvnMerge; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; public class SvnNgMerge extends SvnNgOperationRunner<Void, SvnMerge> { @Override public boolean isApplicable(SvnMerge operation, SvnWcGeneration wcGeneration) throws SVNException { return super.isApplicable(operation, wcGeneration) && !operation.isReintegrate() && operation.getSource() == null && operation.getRevisionRanges() == null && operation.getFirstSource() != null && operation.getSecondSource() != null; } @Override protected Void run(SVNWCContext context) throws SVNException { SvnConflictReport conflictReport; File lockPath = getLockPath(getFirstTarget()); if (getOperation().isDryRun()) { conflictReport = merge(getFirstTarget()); } else { try { lockPath = context.acquireWriteLock(lockPath, false, true); conflictReport = merge(getFirstTarget()); } finally { context.releaseWriteLock(lockPath); sleepForTimestamp(); } } SvnNgMergeDriver.makeMergeConflictError(conflictReport); 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 SvnConflictReport merge(File target) throws SVNException { SVNFileType ft = SVNFileType.getType(target); if (ft == SVNFileType.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "Path ''{0}'' does not exist", target); SVNErrorManager.error(err, SVNLogType.WC); } SvnTarget source1 = getOperation().getFirstSource(); SvnTarget source2 = getOperation().getSecondSource(); if (source1.getResolvedPegRevision() == SVNRevision.UNDEFINED || source2.getResolvedPegRevision() == SVNRevision.UNDEFINED) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Not all revisions are specified"); SVNErrorManager.error(err, SVNLogType.WC); } if (source1.isURL() != source2.isURL()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Merge sources must both be either paths or URLs"); SVNErrorManager.error(err, SVNLogType.WC); } SVNURL url1 = getRepositoryAccess().getTargetURL(source1); if (url1 == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", source1.getFile()); SVNErrorManager.error(err, SVNLogType.WC); } SVNURL url2 = getRepositoryAccess().getTargetURL(source2); if (url2 == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", source2.getFile()); SVNErrorManager.error(err, SVNLogType.WC); } SVNNodeKind targetKind = getWcContext().readKind(target, false); if (targetKind != SVNNodeKind.DIR && targetKind != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Merge target ''{0}'' does not exist in the working copy", target); SVNErrorManager.error(err, SVNLogType.WC); } SvnNgMergeDriver mergeDriver = new SvnNgMergeDriver(getWcContext(), getOperation(), getRepositoryAccess(), getOperation().getMergeOptions()); mergeDriver.ensureWcIsSuitableForMerge(target, getOperation().isAllowMixedRevisions(), true, true); SVNURL wcReposRootURL = getWcContext().getNodeReposInfo(target).reposRootUrl; SVNRepository repos1 = getRepositoryAccess().createRepository(url1, null, false); SVNRepository repos2 = getRepositoryAccess().createRepository(url2, null, false); try { Structure<RevisionsPair> pair = getRepositoryAccess().getRevisionNumber(repos1, SvnTarget.fromURL(url1), source1.getResolvedPegRevision(), null); long rev1 = pair.lng(RevisionsPair.revNumber); pair.release(); pair = getRepositoryAccess().getRevisionNumber(repos2, SvnTarget.fromURL(url2), source2.getResolvedPegRevision(), null); long rev2 = pair.lng(RevisionsPair.revNumber); pair.release(); String uuid1 = repos1.getRepositoryUUID(true); String uuid2 = repos2.getRepositoryUUID(true); if (!uuid1.equals(uuid2)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_UUID_MISMATCH, "''{0}'' isn''t in the same repository as ''{1}''", url1, url2); SVNErrorManager.error(err, SVNLogType.WC); } boolean sameRepos = true; SVNURL sourceReposRootURL = repos1.getRepositoryRoot(true); if (!wcReposRootURL.equals(sourceReposRootURL)) { String targetUuid = getWcContext().getNodeReposInfo(target).reposUuid; sameRepos = targetUuid.equals(uuid1); } SVNLocationSegment yc = null; if (!getOperation().isIgnoreAncestry()) { yc = getRepositoryAccess().getYoungestCommonAncestor(url1, rev1, url2, rev2); } List<MergeSource> sources = new ArrayList<SvnNgMergeDriver.MergeSource>(); boolean sourcesAncestral = false; boolean sourcesRelated = false; if (yc != null && yc.getPath() != null && yc.getStartRevision() >= 0) { sourcesRelated = true; SVNURL ycURL = sourceReposRootURL.appendPath(yc.getPath(), false); if (url2.equals(ycURL) && yc.getStartRevision() == rev2) { sourcesAncestral = true; SVNRevisionRange range = new SVNRevisionRange(SVNRevision.create(rev1), SVNRevision.create(yc.getStartRevision())); Collection<SVNRevisionRange> ranges = new ArrayList<SVNRevisionRange>(); ranges.add(range); sources = mergeDriver.normalizeMergeSources(SvnTarget.fromURL(url1), url1, sourceReposRootURL, SVNRevision.create(rev1), ranges, repos1); } else if (url1.equals(ycURL) && yc.getStartRevision() == rev1) { sourcesAncestral = true; SVNRevisionRange range = new SVNRevisionRange(SVNRevision.create(yc.getStartRevision()), SVNRevision.create(rev2)); Collection<SVNRevisionRange> ranges = new ArrayList<SVNRevisionRange>(); ranges.add(range); sources = mergeDriver.normalizeMergeSources(SvnTarget.fromURL(url2), url2, sourceReposRootURL, SVNRevision.create(rev2), ranges, repos2); } else { mergeDriver.mergeCousinsAndSupplementMergeInfo(target, repos1, repos2, url1, rev1, url2, rev2, yc.getStartRevision(), sourceReposRootURL, wcReposRootURL, getOperation().getDepth(), getOperation().isIgnoreMergeInfo(), getOperation().isIgnoreAncestry(), getOperation().isForce(), getOperation().isRecordOnly(), getOperation().isDryRun()); } } else { MergeSource source = new MergeSource(); source.rev1 = rev1; source.rev2 = rev2; source.url1 = url1; source.url2 = url2; sources.add(source); } SvnNgMergeDriver.MergeData mergeData = mergeDriver.doMerge(null, sources, target, repos1, sourcesRelated, sameRepos, getOperation().isIgnoreMergeInfo(), getOperation().isIgnoreAncestry(), getOperation().isForce(), getOperation().isDryRun(), getOperation().isRecordOnly(), null, false, false, getOperation().getDepth(), getOperation().getMergeOptions()); if (mergeData.useSleep) { SVNFileUtil.sleepForTimestamp(); } return mergeData.conflictReport; } finally { repos1.closeSession(); repos2.closeSession(); } } }