package org.tmatesoft.svn.core.internal.wc2.ng; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; 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.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.SVNPropertyValue; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.internal.util.SVNDate; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.util.SVNURLUtil; import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; import org.tmatesoft.svn.core.internal.wc.SVNConflictVersion; 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.SVNWCContext.MergeInfo; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.MergePropertiesInfo; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.SVNWCNodeReposInfo; 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.ng.SvnNgMergeDriver.ObstructionState; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.ISVNConflictHandler; import org.tmatesoft.svn.core.wc.ISVNOptions; import org.tmatesoft.svn.core.wc.SVNConflictAction; import org.tmatesoft.svn.core.wc.SVNConflictChoice; import org.tmatesoft.svn.core.wc.SVNConflictDescription; import org.tmatesoft.svn.core.wc.SVNConflictReason; import org.tmatesoft.svn.core.wc.SVNConflictResult; import org.tmatesoft.svn.core.wc.SVNDiffOptions; import org.tmatesoft.svn.core.wc.SVNOperation; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; public class SvnNgMergeCallback implements ISvnDiffCallback { private Collection<File> conflictedPaths; private SvnNgMergeDriver driver; public SvnNgMergeCallback(SvnNgMergeDriver driver) { this.driver = driver; } public Collection<File> getConflictedPaths() { return conflictedPaths; } public void fileOpened(SvnDiffCallbackResult result, File path, long revision) throws SVNException { // do nothing } public void fileChanged(SvnDiffCallbackResult result, final File path, File tmpFile1, File tmpFile2, long rev1, long rev2, String mimetype1, String mimeType2, SVNProperties propChanges, SVNProperties originalProperties) throws SVNException { ObstructionState os = driver.performObstructionCheck(path, SVNNodeKind.UNKNOWN); if (os.obstructionState != SVNStatusType.INAPPLICABLE) { result.contentState = os.obstructionState; if (os.obstructionState == SVNStatusType.MISSING) { result.propState = SVNStatusType.MISSING; } return; } SVNNodeKind wcKind = os.kind; boolean isDeleted = os.deleted; if (wcKind != SVNNodeKind.FILE || isDeleted) { if (wcKind == SVNNodeKind.NONE) { SVNDepth parentDepth = getContext().getNodeDepth(SVNFileUtil.getParentFile(path)); if (parentDepth != SVNDepth.UNKNOWN && parentDepth.compareTo(SVNDepth.FILES) < 0) { result.contentState = SVNStatusType.MISSING; result.propState = SVNStatusType.MISSING; return; } } treeConflict(path, SVNNodeKind.FILE, SVNConflictAction.EDIT, SVNConflictReason.MISSING); result.treeConflicted = true; result.contentState = SVNStatusType.MISSING; result.propState = SVNStatusType.MISSING; return; } if (!propChanges.isEmpty()) { MergePropertiesInfo mergeOutcome = mergePropChanges(path, propChanges, originalProperties); result.propState = mergeOutcome != null ? mergeOutcome.mergeOutcome : null; if (mergeOutcome != null && mergeOutcome.treeConflicted) { result.treeConflicted = true; return; } } else { result.propState = SVNStatusType.UNCHANGED; } if (isRecordOnly()) { result.contentState = SVNStatusType.UNCHANGED; return; } if (tmpFile1 != null) { boolean hasLocalMods = getContext().isTextModified(path, false); String targetLabel = ".working"; String leftLabel = ".merge-left.r" + rev1; String rightLabel = ".merge-right.r" + rev2; SVNConflictVersion[] cvs = makeConflictVersions(path, SVNNodeKind.FILE); ISVNOptions opts = getContext().getOptions(); final ISVNConflictHandler[] conflictHandler = new ISVNConflictHandler[1]; if (opts instanceof DefaultSVNOptions) { conflictHandler[0] = ((DefaultSVNOptions) opts).getConflictResolver(); ((DefaultSVNOptions) opts).setConflictHandler(new ISVNConflictHandler() { public SVNConflictResult handleConflict(SVNConflictDescription conflictDescription) throws SVNException { SVNConflictResult result = conflictHandler[0] == null ? new SVNConflictResult(SVNConflictChoice.POSTPONE, null) : conflictHandler[0].handleConflict(conflictDescription); if (result != null && result.getConflictChoice() == SVNConflictChoice.POSTPONE) { if (conflictedPaths == null) { conflictedPaths = new HashSet<File>(); } conflictedPaths.add(path); } return result; } }); } try { MergeInfo mergeOutcome = getContext().mergeText(tmpFile1, tmpFile2, path, leftLabel, rightLabel, targetLabel, cvs[0], cvs[1], isDryRun(), getDiffOptions(), propChanges); if (mergeOutcome.mergeOutcome == SVNStatusType.CONFLICTED) { result.contentState = SVNStatusType.CONFLICTED; } else if (hasLocalMods && mergeOutcome.mergeOutcome != SVNStatusType.UNCHANGED) { result.contentState = SVNStatusType.MERGED; } else if (mergeOutcome.mergeOutcome == SVNStatusType.MERGED) { result.contentState = SVNStatusType.CHANGED; } else if (mergeOutcome.mergeOutcome == SVNStatusType.NO_MERGE) { result.contentState = SVNStatusType.MISSING; } else { result.contentState = SVNStatusType.UNCHANGED; } } finally { if (opts instanceof DefaultSVNOptions) { ((DefaultSVNOptions) opts).setConflictHandler(conflictHandler[0]); } } } } public void fileAdded(SvnDiffCallbackResult result, File path, File leftFile, File rightFile, long rev1, long rev2, String mimeType1, String mimeType2, File copyFromPath, long copyFromRevision, SVNProperties propChanges, SVNProperties originalProperties) throws SVNException { if (isRecordOnly()) { result.contentState = SVNStatusType.UNCHANGED; result.propState = SVNStatusType.UNCHANGED; return; } result.propState = SVNStatusType.UNKNOWN; SVNProperties fileProps = new SVNProperties(originalProperties); for (String propName : propChanges.nameSet()) { if (SVNProperty.isWorkingCopyProperty(propName)) { continue; } if (!isSameRepos() && !SVNProperty.isRegularProperty(propName)) { continue; } if (!isSameRepos() && SVNProperty.MERGE_INFO.equals(propName)) { continue; } if (propChanges.getSVNPropertyValue(propName) != null) { fileProps.put(propName, propChanges.getSVNPropertyValue(propName)); } else { fileProps.remove(propName); } } ObstructionState os = driver.performObstructionCheck(path, SVNNodeKind.UNKNOWN); if (os.obstructionState != SVNStatusType.INAPPLICABLE) { if (isDryRun() && getAddedPath() != null && SVNWCUtils.isChild(getAddedPath(), path)) { result.contentState = SVNStatusType.CHANGED; if (!fileProps.isEmpty()) { result.propState = SVNStatusType.CHANGED; } } else { result.contentState = os.obstructionState; } return; } SVNNodeKind kind = SVNFileType.getNodeKind(SVNFileType.getType(path)); if (kind == SVNNodeKind.NONE) { if (!isDryRun()) { SVNURL copyFromUrl = null; long copyFromRev = -1; InputStream newBaseContents = null; InputStream newContents = null; SVNProperties newBaseProps, newProps; try { if (isSameRepos()) { String child = SVNWCUtils.getPathAsChild(getTargetPath(), path); if (child != null) { copyFromUrl = getSource2URL().appendPath(child, false); } else { copyFromUrl = getSource2URL(); } copyFromRev = rev2; checkReposMatch(path, copyFromUrl); newBaseContents = SVNFileUtil.openFileForReading(rightFile); newContents = null; newBaseProps = fileProps; newProps = null; } else { newBaseProps = new SVNProperties(); newProps = fileProps; newBaseContents = SVNFileUtil.DUMMY_IN; newContents = SVNFileUtil.openFileForReading(rightFile); } SVNTreeConflictDescription tc = getContext().getTreeConflict(path); if (tc != null) { treeConflictOnAdd(path, SVNNodeKind.FILE, SVNConflictAction.ADD, SVNConflictReason.ADDED); result.treeConflicted = true; } else { SvnNgReposToWcCopy.addFileToWc(getContext(), path, newBaseContents, newContents, newBaseProps, newProps, copyFromUrl, copyFromRev); } } finally { SVNFileUtil.closeFile(newBaseContents); SVNFileUtil.closeFile(newContents); } } result.contentState = SVNStatusType.CHANGED; if (!fileProps.isEmpty()) { result.propState = SVNStatusType.CHANGED; } } else if (kind == SVNNodeKind.DIR) { treeConflictOnAdd(path, SVNNodeKind.FILE, SVNConflictAction.ADD, SVNConflictReason.OBSTRUCTED); result.treeConflicted = true; SVNNodeKind wcKind = getContext().readKind(path, false); if (wcKind != SVNNodeKind.NONE && driver.isDryRunDeletion(path)) { result.contentState = SVNStatusType.CHANGED; } else { result.contentState = SVNStatusType.OBSTRUCTED; } } else if (kind == SVNNodeKind.FILE) { if (driver.isDryRunDeletion(path)) { result.contentState = SVNStatusType.CHANGED; } else { treeConflictOnAdd(path, SVNNodeKind.FILE, SVNConflictAction.ADD, SVNConflictReason.ADDED); result.treeConflicted = true; } } else { result.contentState = SVNStatusType.UNKNOWN; } } public void fileDeleted(SvnDiffCallbackResult result, File path, File leftFile, File rightFile, String mimeType1, String mimeType2, SVNProperties originalProperties) throws SVNException { if (isDryRun()) { if (getDryRunDeletions() == null) { setDryRunDeletions(new HashSet<File>()); } getDryRunDeletions().add(path); } if (isRecordOnly()) { result.contentState = SVNStatusType.UNCHANGED; return; } ObstructionState os = driver.performObstructionCheck(path, SVNNodeKind.UNKNOWN); if (os.obstructionState != SVNStatusType.INAPPLICABLE) { result.contentState = os.obstructionState; return; } SVNNodeKind kind = SVNFileType.getNodeKind(SVNFileType.getType(path)); if (kind == SVNNodeKind.FILE) { boolean same = compareFiles(leftFile, originalProperties, path); if (same || isForce() || isRecordOnly()) { if (!isDryRun()) { SvnNgRemove.delete(getContext(), path, null, false, true, null); } result.contentState = SVNStatusType.CHANGED; } else { treeConflict(path, SVNNodeKind.FILE, SVNConflictAction.DELETE, SVNConflictReason.EDITED); result.treeConflicted = true; result.contentState = SVNStatusType.OBSTRUCTED; } } else if (kind == SVNNodeKind.DIR) { treeConflict(path, SVNNodeKind.FILE, SVNConflictAction.DELETE, SVNConflictReason.OBSTRUCTED); result.treeConflicted = true; result.contentState = SVNStatusType.OBSTRUCTED; } else if (kind == SVNNodeKind.NONE) { treeConflict(path, SVNNodeKind.FILE, SVNConflictAction.DELETE, SVNConflictReason.DELETED); result.treeConflicted = true; result.contentState = SVNStatusType.MISSING; } else { result.contentState = SVNStatusType.UNKNOWN; } } private void setDryRunDeletions(Collection<File> set) { driver.dryRunDeletions = set; } private void setDryRunAddtions(Collection<File> set) { driver.dryRunAdded = set; } public void dirDeleted(SvnDiffCallbackResult result, File path) throws SVNException { if (isRecordOnly()) { result.contentState = SVNStatusType.UNCHANGED; return; } ObstructionState os = driver.performObstructionCheck(path, SVNNodeKind.UNKNOWN); boolean isVersioned = os.kind == SVNNodeKind.DIR || os.kind == SVNNodeKind.FILE; if (os.obstructionState != SVNStatusType.INAPPLICABLE) { result.contentState = os.obstructionState; return; } if (os.deleted) { os.kind = SVNNodeKind.NONE; } if (isDryRun()) { if (getDryRunDeletions() == null) { setDryRunDeletions(new HashSet<File>()); } getDryRunDeletions().add(path); } if (os.kind == SVNNodeKind.DIR) { if (isVersioned && !os.deleted) { try { if (!isForce()) { SvnNgRemove.checkCanDelete(driver.operation.getOperationFactory(), getContext(), path); } if (!isDryRun()) { SvnNgRemove.delete(getContext(), path, null, false, false, null); } result.contentState = SVNStatusType.CHANGED; } catch (SVNException e) { treeConflict(path, SVNNodeKind.DIR, SVNConflictAction.DELETE, SVNConflictReason.EDITED); result.treeConflicted = true; result.contentState = SVNStatusType.CONFLICTED; } } else { treeConflict(path, SVNNodeKind.DIR, SVNConflictAction.DELETE, SVNConflictReason.DELETED); result.treeConflicted = true; } } else if (os.kind == SVNNodeKind.FILE) { result.contentState = SVNStatusType.OBSTRUCTED; } else if (os.kind == SVNNodeKind.NONE) { treeConflict(path, SVNNodeKind.DIR, SVNConflictAction.DELETE, SVNConflictReason.DELETED); result.treeConflicted = true; result.contentState = SVNStatusType.MISSING; } else { result.contentState = SVNStatusType.UNKNOWN; } } public void dirOpened(SvnDiffCallbackResult result, File path, long revision) throws SVNException { ObstructionState os = driver.performObstructionCheck(path, SVNNodeKind.UNKNOWN); if (os.obstructionState != SVNStatusType.INAPPLICABLE) { result.skipChildren = true; return; } if (os.kind != SVNNodeKind.DIR || os.deleted) { if (os.kind == SVNNodeKind.NONE) { SVNDepth parentDepth = getContext().getNodeDepth(SVNFileUtil.getParentFile(path)); if (parentDepth != SVNDepth.UNKNOWN && parentDepth.compareTo(SVNDepth.IMMEDIATES) < 0) { result.skipChildren = true; return; } } if (os.kind == SVNNodeKind.FILE) { treeConflict(path, SVNNodeKind.DIR, SVNConflictAction.EDIT, SVNConflictReason.REPLACED); result.treeConflicted = true; } else if (os.deleted || os.kind == SVNNodeKind.NONE) { treeConflict(path, SVNNodeKind.DIR, SVNConflictAction.EDIT, SVNConflictReason.DELETED); result.treeConflicted = true; } } } public void dirAdded(SvnDiffCallbackResult result, File path, long revision, String copyFromPath, long copyFromRevision) throws SVNException { if (isRecordOnly()) { result.contentState = SVNStatusType.UNCHANGED; return; } File parentPath = SVNFileUtil.getParentFile(path); String child = SVNWCUtils.getPathAsChild(getTargetPath(), path); SVNURL copyFromUrl = null; long copyFromRev = -1; if (isSameRepos()) { copyFromUrl = getSource2URL().appendPath(child, false); copyFromRev = revision; checkReposMatch(parentPath, copyFromUrl); } ObstructionState os = driver.performObstructionCheck(path, SVNNodeKind.UNKNOWN); boolean isVersioned = os.kind == SVNNodeKind.DIR || os.kind == SVNNodeKind.FILE; if (os.obstructionState == SVNStatusType.OBSTRUCTED && (os.deleted || os.kind == SVNNodeKind.NONE)) { SVNFileType diskKind = SVNFileType.getType(path); if (diskKind == SVNFileType.DIRECTORY) { os.obstructionState = SVNStatusType.INAPPLICABLE; os.kind = SVNNodeKind.DIR; } } if (os.obstructionState != SVNStatusType.INAPPLICABLE) { if (isDryRun() && getAddedPath() != null && SVNWCUtils.isChild(getAddedPath(), path)) { result.contentState = SVNStatusType.CHANGED; } else { result.contentState = os.obstructionState; } return; } if (os.deleted) { os.kind = SVNNodeKind.NONE; } if (os.kind == SVNNodeKind.NONE) { if (isDryRun()) { if (getDryRunAdditions() == null) { setDryRunAddtions(new HashSet<File>()); } getDryRunAdditions().add(path); setAddedPath(path); } else { path.mkdir(); if (copyFromUrl != null) { SVNWCNodeReposInfo reposInfo = getContext().getNodeReposInfo(parentPath); File reposRelPath = new File(SVNURLUtil.getRelativeURL(reposInfo.reposRootUrl, copyFromUrl, false)); getContext().getDb().opCopyDir(path, new SVNProperties(), copyFromRev, new SVNDate(0, 0), null, reposRelPath, reposInfo.reposRootUrl, reposInfo.reposUuid, copyFromRev, null, false, SVNDepth.INFINITY, null, null); } else { getContext().getDb().opAddDirectory(path, null, null); } } result.contentState = SVNStatusType.CHANGED; } else if (os.kind == SVNNodeKind.DIR) { if (!isVersioned || os.deleted) { if (!isDryRun()) { if (copyFromUrl != null) { SVNWCNodeReposInfo reposInfo = getContext().getNodeReposInfo(parentPath); File reposRelPath = new File(SVNURLUtil.getRelativeURL(reposInfo.reposRootUrl, copyFromUrl, false)); getContext().getDb().opCopyDir(path, new SVNProperties(), copyFromRev, new SVNDate(0, 0), null, reposRelPath, reposInfo.reposRootUrl, reposInfo.reposUuid, copyFromRev, null, false, SVNDepth.INFINITY, null, null); } else { getContext().getDb().opAddDirectory(path, null, null); } } else { setAddedPath(path); } result.contentState = SVNStatusType.CHANGED; } else { if (driver.isDryRunDeletion(path)) { result.contentState = SVNStatusType.CHANGED; } else { treeConflictOnAdd(path, SVNNodeKind.DIR, SVNConflictAction.ADD, SVNConflictReason.ADDED); result.treeConflicted = true; result.contentState = SVNStatusType.OBSTRUCTED; } } } else if (os.kind == SVNNodeKind.FILE) { if (isDryRun()) { setAddedPath(null); } if (isVersioned && driver.isDryRunDeletion(path)) { result.contentState = SVNStatusType.CHANGED; } else { treeConflictOnAdd(path, SVNNodeKind.DIR, SVNConflictAction.ADD, SVNConflictReason.OBSTRUCTED); result.treeConflicted = true; result.contentState = SVNStatusType.OBSTRUCTED; } } else { if (isDryRun()) { setAddedPath(null); } result.contentState = SVNStatusType.UNKNOWN; } } public void dirPropsChanged(SvnDiffCallbackResult result, File path, boolean isAdded, SVNProperties propChanges, SVNProperties originalProperties) throws SVNException { ObstructionState os = driver.performObstructionCheck(path, SVNNodeKind.DIR); if (os.obstructionState != SVNStatusType.INAPPLICABLE) { result.propState = os.obstructionState; return; } if (isAdded && isDryRun() && driver.isDryRunAddition(path)) { return; } MergePropertiesInfo info = mergePropChanges(path, propChanges, originalProperties); result.treeConflicted = info != null ? info.treeConflicted : false; result.propState = info != null ? info.mergeOutcome : null; } public void dirClosed(SvnDiffCallbackResult result, File path, boolean isAdded) throws SVNException { if (isDryRun() && getDryRunDeletions() != null) { getDryRunDeletions().clear(); } } private void checkReposMatch(File path, SVNURL url) throws SVNException { if (!SVNURLUtil.isAncestor(getReposRootURL(), url)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Url ''{0}'' of ''{1}'' is not in repository ''{2}''", url, path, getReposRootURL()); SVNErrorManager.error(err, SVNLogType.WC); } } private MergePropertiesInfo mergePropChanges(File localAbsPath, SVNProperties propChanges, SVNProperties originalProperties) throws SVNException { SVNProperties props = new SVNProperties(); SvnNgPropertiesManager.categorizeProperties(propChanges, props, null, null); if (isRecordOnly() && !props.isEmpty()) { SVNProperties mergeinfoProps = new SVNProperties(); if (props.containsName(SVNProperty.MERGE_INFO)) { mergeinfoProps.put(SVNProperty.MERGE_INFO, props.getStringValue(SVNProperty.MERGE_INFO)); } props = mergeinfoProps; } MergePropertiesInfo mergeOutcome = null; if (!props.isEmpty()) { if (getSource1Rev() < getSource2Rev() || !areSourcesAncestral()) { props = filterSelfReferentialMergeInfo(props, localAbsPath, isHonorMergeInfo(), isSameRepos(), isReintegrateMerge(), getRepos2()); } SVNException err = null; try { mergeOutcome = getContext().mergeProperties(localAbsPath, null, null, originalProperties, props, isDryRun(), getContext().getOptions().getConflictResolver()); } catch (SVNException e) { err = e; } if (!isDryRun()) { for (String propName : props.nameSet()) { if (!SVNProperty.MERGE_INFO.equals(propName)) { continue; } SVNProperties pristineProps = getContext().getPristineProps(localAbsPath); boolean hasPristineMergeInfo = false; if (pristineProps != null && pristineProps.containsName(SVNProperty.MERGE_INFO)) { hasPristineMergeInfo = true; } if (!hasPristineMergeInfo && props.getSVNPropertyValue(propName) != null) { addPathWithAddedMergeInfo(localAbsPath); } else if (hasPristineMergeInfo && props.getSVNPropertyValue(propName) == null) { addPathWithDeletedMergeInfo(localAbsPath); } } } if (err != null && (err.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND || err.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_UNEXPECTED_STATUS)) { if (mergeOutcome != null) { mergeOutcome.mergeOutcome = SVNStatusType.MISSING; mergeOutcome.treeConflicted = true; } } else if (err != null) { throw err; } } return mergeOutcome; } private void addPathWithAddedMergeInfo(File localAbsPath) { if (driver.pathsWithNewMergeInfo == null) { driver.pathsWithNewMergeInfo = new HashSet<File>(); } driver.pathsWithNewMergeInfo.add(localAbsPath); } private void addPathWithDeletedMergeInfo(File localAbsPath) { if (driver.pathsWithDeletedMergeInfo == null) { driver.pathsWithDeletedMergeInfo = new HashSet<File>(); } driver.pathsWithDeletedMergeInfo.add(localAbsPath); } private SVNProperties filterSelfReferentialMergeInfo(SVNProperties props, File localAbsPath, boolean honorMergeInfo, boolean sameRepos, boolean reintegrateMerge, SVNRepository repos) throws SVNException { if (!sameRepos) { return omitMergeInfoChanges(props); } if (!honorMergeInfo && !reintegrateMerge) { return props; } boolean isAdded = getContext().isNodeAdded(localAbsPath); if (isAdded) { return props; } long baseRevision = getContext().getNodeBaseRev(localAbsPath); SVNProperties adjustedProps = new SVNProperties(); for (String propName : props.nameSet()) { if (!SVNProperty.MERGE_INFO.equals(propName) || props.getSVNPropertyValue(propName) == null || "".equals(props.getSVNPropertyValue(propName))) { adjustedProps.put(propName, props.getSVNPropertyValue(propName)); continue; } SVNURL targetUrl = getContext().getUrlFromPath(localAbsPath); SVNURL oldUrl = repos.getLocation(); repos.setLocation(targetUrl, false); String mi = props.getStringValue(propName); Map<String, SVNMergeRangeList> mergeinfo = null; Map<String, SVNMergeRangeList> filteredYoungerMergeinfo = null; Map<String, SVNMergeRangeList> filteredMergeinfo = null; try { mergeinfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(mi), null); } catch (SVNException e) { adjustedProps.put(propName, props.getSVNPropertyValue(propName)); if (e.getErrorMessage().getErrorCode() == SVNErrorCode.MERGE_INFO_PARSE_ERROR) { repos.setLocation(oldUrl, false); continue; } throw e; } Map<String, SVNMergeRangeList>[] splitted = splitMergeInfoOnRevision(mergeinfo, baseRevision); Map<String, SVNMergeRangeList> youngerMergeInfo = splitted[0]; mergeinfo = splitted[1]; if (youngerMergeInfo != null) { SVNURL mergeSourceRootUrl = repos.getRepositoryRoot(true); for (Iterator<String> youngerMergeInfoIter = youngerMergeInfo.keySet().iterator(); youngerMergeInfoIter.hasNext();) { String sourcePath = youngerMergeInfoIter.next(); SVNMergeRangeList rangeList = (SVNMergeRangeList) youngerMergeInfo.get(sourcePath); SVNMergeRange ranges[] = rangeList.getRanges(); List<SVNMergeRange> adjustedRanges = new ArrayList<SVNMergeRange>(); SVNURL mergeSourceURL = mergeSourceRootUrl.appendPath(sourcePath, false); for (int i = 0; i < ranges.length; i++) { SVNMergeRange range = ranges[i]; Structure<LocationsInfo> locations = null; try { locations = new SvnNgRepositoryAccess(null, getContext()).getLocations( repos, SvnTarget.fromURL(targetUrl), SVNRevision.create(baseRevision), SVNRevision.create(range.getStartRevision() + 1), SVNRevision.UNDEFINED); SVNURL startURL = locations.get(LocationsInfo.startUrl); if (!mergeSourceURL.equals(startURL)) { adjustedRanges.add(range); } locations.release(); } catch (SVNException svne) { SVNErrorCode code = svne.getErrorMessage().getErrorCode(); if (code == SVNErrorCode.CLIENT_UNRELATED_RESOURCES || code == SVNErrorCode.RA_DAV_PATH_NOT_FOUND || code == SVNErrorCode.FS_NOT_FOUND || code == SVNErrorCode.FS_NO_SUCH_REVISION) { adjustedRanges.add(range); } else { throw svne; } } } if (!adjustedRanges.isEmpty()) { if (filteredYoungerMergeinfo == null) { filteredYoungerMergeinfo = new TreeMap<String, SVNMergeRangeList>(); } SVNMergeRangeList adjustedRangeList = SVNMergeRangeList.fromCollection(adjustedRanges); filteredYoungerMergeinfo.put(sourcePath, adjustedRangeList); } } } if (mergeinfo != null && !mergeinfo.isEmpty()) { Map<String, SVNMergeRangeList> implicitMergeInfo = getRepositoryAccess().getHistoryAsMergeInfo(getRepos2(), SvnTarget.fromFile(localAbsPath), baseRevision, -1); filteredMergeinfo = SVNMergeInfoUtil.removeMergeInfo(implicitMergeInfo, mergeinfo, true); } if (oldUrl != null) { repos.setLocation(oldUrl, false); } if (filteredMergeinfo != null && filteredYoungerMergeinfo != null) { filteredMergeinfo = SVNMergeInfoUtil.mergeMergeInfos(filteredMergeinfo, filteredYoungerMergeinfo); } else if (filteredYoungerMergeinfo != null) { filteredMergeinfo = filteredYoungerMergeinfo; } if (filteredMergeinfo != null && !filteredMergeinfo.isEmpty()) { String filteredMergeInfoStr = SVNMergeInfoUtil.formatMergeInfoToString(filteredMergeinfo, null); adjustedProps.put(SVNProperty.MERGE_INFO, filteredMergeInfoStr); } } return adjustedProps; } private Map<String, SVNMergeRangeList>[] splitMergeInfoOnRevision(Map<String, SVNMergeRangeList> mergeinfo, long revision) { Map<String, SVNMergeRangeList> youngerMergeinfo = null; for (String path : new HashSet<String>(mergeinfo.keySet())) { SVNMergeRangeList rl = mergeinfo.get(path); for (int i = 0; i < rl.getSize(); i++) { SVNMergeRange r = rl.getRanges()[i]; if (r.getEndRevision() <= revision) { continue; } else { SVNMergeRangeList youngerRl = new SVNMergeRangeList(new SVNMergeRange[0]); for (int j = 0; j < rl.getSize(); j++) { SVNMergeRange r2 = rl.getRanges()[j]; SVNMergeRange youngerRange = r2.dup(); if (i == j && r.getStartRevision() + 1 <= revision) { youngerRange.setStartRevision(revision); r.setEndRevision(revision); } youngerRl.pushRange(youngerRange.getStartRevision(), youngerRange.getEndRevision(), youngerRange.isInheritable()); } if (youngerMergeinfo == null) { youngerMergeinfo = new TreeMap<String, SVNMergeRangeList>(); } youngerMergeinfo.put(path, youngerRl); mergeinfo = SVNMergeInfoUtil.removeMergeInfo(youngerMergeinfo, mergeinfo, true); break; } } } @SuppressWarnings("unchecked") Map<String, SVNMergeRangeList>[] result = new Map[2]; result[0] = youngerMergeinfo; result[1] = mergeinfo; return result; } protected static SVNProperties omitMergeInfoChanges(SVNProperties props) { SVNProperties result = new SVNProperties(); for (String name : props.nameSet()) { if (SVNProperty.MERGE_INFO.equals(name)) { continue; } SVNPropertyValue pv = props.getSVNPropertyValue(name); result.put(name, pv); } return result; } private boolean isHonorMergeInfo() { return driver.isHonorMergeInfo(); } private SVNConflictVersion[] makeConflictVersions(File target, SVNNodeKind kind) throws SVNException { SVNURL srcReposUrl = getRepos1().getRepositoryRoot(true); String child = SVNWCUtils.getPathAsChild(getTargetPath(), target); SVNURL leftUrl; SVNURL rightUrl; if (child != null) { leftUrl = getSource1URL().appendPath(child, false); rightUrl = getSource2URL().appendPath(child, false); } else { leftUrl = getSource1URL(); rightUrl = getSource2URL(); } String leftPath = SVNWCUtils.isChild(srcReposUrl, leftUrl); String rightPath = SVNWCUtils.isChild(srcReposUrl, rightUrl); SVNConflictVersion lv = new SVNConflictVersion(srcReposUrl, leftPath, getSource1Rev(), kind); SVNConflictVersion rv = new SVNConflictVersion(srcReposUrl, rightPath, getSource2Rev(), kind); return new SVNConflictVersion[] {lv, rv}; } private void treeConflictOnAdd(File path, SVNNodeKind kind, SVNConflictAction action, SVNConflictReason reason) throws SVNException { if (isRecordOnly() || isDryRun()) { return; } SVNTreeConflictDescription tc = makeTreeConflict(path, kind, action, reason); SVNTreeConflictDescription existingTc = getContext().getTreeConflict(path); if (existingTc == null) { getContext().getDb().opSetTreeConflict(path, tc); if (conflictedPaths == null) { conflictedPaths = new HashSet<File>(); } conflictedPaths.add(path); } else if (existingTc.getConflictAction() == SVNConflictAction.DELETE && tc.getConflictAction() == SVNConflictAction.ADD) { existingTc.setConflictAction(SVNConflictAction.REPLACE); getContext().getDb().opSetTreeConflict(path, existingTc); } } private SVNTreeConflictDescription makeTreeConflict(File path, SVNNodeKind kind, SVNConflictAction action, SVNConflictReason reason) throws SVNException { final SVNConflictVersion[] cvs = makeConflictVersions(path, kind); final SVNTreeConflictDescription tc = new SVNTreeConflictDescription(path, kind, action, reason, SVNOperation.MERGE, cvs[0], cvs[1]); return tc; } private void treeConflict(File path, SVNNodeKind kind, SVNConflictAction action, SVNConflictReason reason) throws SVNException { if (isRecordOnly() || isDryRun()) { return; } SVNTreeConflictDescription tc = getContext().getTreeConflict(path); if (tc == null) { tc = makeTreeConflict(path, kind, action, reason); getContext().getDb().opSetTreeConflict(path, tc); if (conflictedPaths == null) { conflictedPaths = new HashSet<File>(); } conflictedPaths.add(path); } } private boolean compareProps(SVNProperties p1, SVNProperties p2) throws SVNException { if (p1 == null || p2 == null) { return p1 == p2; } SVNProperties diff = p1.compareTo(p2); for (String propName : diff.nameSet()) { if (SVNProperty.isRegularProperty(propName) && !SVNProperty.MERGE_INFO.equals(propName)) { return false; } } return true; } private boolean compareFiles(File oldPath, SVNProperties oldProps, File minePath) throws SVNException { SVNProperties workingProperties = getContext().getActualProps(minePath); boolean same = compareProps(oldProps, workingProperties); if (same) { InputStream is = null; InputStream old = null; try { if (workingProperties != null && workingProperties.getStringValue(SVNProperty.SPECIAL) != null) { is = SVNFileUtil.readSymlink(minePath); } else { is = getContext().getTranslatedStream(minePath, minePath, true, false); } old = SVNFileUtil.openFileForReading(oldPath); same = SVNFileUtil.compare(is, old); } finally { SVNFileUtil.closeFile(is); SVNFileUtil.closeFile(old); } } return same; } private SVNWCContext getContext() { return driver.context; } private boolean isReintegrateMerge() { return driver.reintegrateMerge; } private boolean isRecordOnly() { return driver.recordOnly; } private boolean isDryRun() { return driver.dryRun; } private boolean isForce() { return driver.forceDelete; } private boolean isSameRepos() { return driver.sameRepos; } private SVNDiffOptions getDiffOptions() { return driver.diffOptions; } private File getAddedPath() { return driver.addedPath; } private void setAddedPath(File path) { driver.addedPath = path; } private boolean areSourcesAncestral() { return driver.sourcesAncestral; } private File getTargetPath() { return driver.targetAbsPath; } private SVNRepository getRepos1() { return driver.repos1; } private SVNRepository getRepos2() { return driver.repos2; } private SVNURL getReposRootURL() { return driver.reposRootUrl; } private SvnRepositoryAccess getRepositoryAccess() { return driver.repositoryAccess; } private SVNURL getSource1URL() { return driver.mergeSource.url1; } private SVNURL getSource2URL() { return driver.mergeSource.url2; } private long getSource1Rev() { return driver.mergeSource.rev1; } private long getSource2Rev() { return driver.mergeSource.rev2; } private Collection<File> getDryRunDeletions() { return driver.dryRunDeletions; } private Collection<File> getDryRunAdditions() { return driver.dryRunAdded; } }