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();
}
}
}