/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.wc; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.text.MessageFormat; 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.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNProperty; import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea; import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry; import org.tmatesoft.svn.core.internal.wc.admin.SVNLog; import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator; import org.tmatesoft.svn.core.wc.ISVNConflictHandler; import org.tmatesoft.svn.core.wc.ISVNMerger; 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.SVNMergeFileSet; import org.tmatesoft.svn.core.wc.SVNMergeResult; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.tmatesoft.svn.core.wc.SVNTextConflictDescription; import org.tmatesoft.svn.core.wc.SVNPropertyConflictDescription; import org.tmatesoft.svn.util.SVNLogType; import de.regnis.q.sequence.line.QSequenceLineRAByteData; import de.regnis.q.sequence.line.QSequenceLineRAData; import de.regnis.q.sequence.line.QSequenceLineRAFileData; /** * @version 1.3 * @author TMate Software Ltd. * @since 1.2.0 */ public class DefaultSVNMerger extends AbstractSVNMerger implements ISVNMerger { private static List STATUS_ORDERING = new LinkedList(); static { STATUS_ORDERING.add(SVNStatusType.UNKNOWN); STATUS_ORDERING.add(SVNStatusType.UNCHANGED); STATUS_ORDERING.add(SVNStatusType.INAPPLICABLE); STATUS_ORDERING.add(SVNStatusType.CHANGED); STATUS_ORDERING.add(SVNStatusType.MERGED); STATUS_ORDERING.add(SVNStatusType.OBSTRUCTED); STATUS_ORDERING.add(SVNStatusType.CONFLICTED); } private ISVNConflictHandler myConflictCallback; private SVNDiffConflictChoiceStyle myDiffConflictStyle; /** * * @param start * @param sep * @param end * @param callback * @deprecated use {@link #DefaultSVNMerger(byte[], byte[], byte[], ISVNConflictHandler, SVNDiffConflictChoiceStyle)} instead */ public DefaultSVNMerger(byte[] start, byte[] sep, byte[] end) { this(start, sep, end, null); } /** * * @param start * @param sep * @param end * @param callback * @deprecated use {@link #DefaultSVNMerger(byte[], byte[], byte[], ISVNConflictHandler, SVNDiffConflictChoiceStyle)} instead */ public DefaultSVNMerger(byte[] start, byte[] sep, byte[] end, ISVNConflictHandler callback) { this(start, sep, end, callback, SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST); } public DefaultSVNMerger(byte[] start, byte[] sep, byte[] end, ISVNConflictHandler callback, SVNDiffConflictChoiceStyle style) { super(start, sep, end); myConflictCallback = callback; myDiffConflictStyle = style; } public SVNMergeResult mergeProperties(String localPath, SVNProperties workingProperties, SVNProperties baseProperties, SVNProperties serverBaseProps, SVNProperties propDiff, SVNAdminArea adminArea, SVNLog log, boolean baseMerge, boolean dryRun) throws SVNException { propDiff = propDiff == null ? new SVNProperties() : propDiff; if (baseProperties == null) { baseProperties = adminArea.getBaseProperties(localPath).asMap(); } if (workingProperties == null) { workingProperties = adminArea.getProperties(localPath).asMap(); } if (serverBaseProps == null) { serverBaseProps = baseProperties != null ? new SVNProperties(baseProperties) : new SVNProperties(); } boolean isDir = adminArea.getThisDirName().equals(localPath); List conflicts = new LinkedList(); List conflict = new LinkedList(); SVNStatusType status = SVNStatusType.UNCHANGED; for (Iterator propEntries = propDiff.nameSet().iterator(); propEntries.hasNext();) { String propName = (String) propEntries.next(); SVNPropertyValue toValue = propDiff.getSVNPropertyValue(propName); SVNPropertyValue fromValue = serverBaseProps.getSVNPropertyValue(propName); SVNPropertyValue workingValue = workingProperties.getSVNPropertyValue(propName); SVNPropertyValue baseValue = baseProperties.getSVNPropertyValue(propName); boolean isNormal = SVNProperty.isRegularProperty(propName); if (baseMerge) { changeProperty(baseProperties, propName, toValue); } if (isNormal) { status = getPropMergeStatus(status, SVNStatusType.CHANGED); } SVNStatusType newStatus = null; if (fromValue == null) { newStatus = applySinglePropertyAdd(localPath, isDir, isNormal ? status : null, workingProperties, propName, baseValue, toValue, workingValue, adminArea, log, conflict, dryRun); } else if (toValue == null) { newStatus = applySinglePropertyDelete(localPath, isDir, isNormal ? status : null, workingProperties, propName, baseValue, fromValue, workingValue, adminArea, log, conflict, dryRun); } else { newStatus = applySinglePropertyChange(localPath, isDir, status, workingProperties, propName, baseValue, fromValue, toValue, workingValue, adminArea, log, conflict, dryRun); } if (isNormal) { status = newStatus; } if (!conflict.isEmpty()) { if (isNormal) { status = getPropMergeStatus(status, SVNStatusType.CONFLICTED); } Object conflictDescription = conflict.remove(0); if (dryRun) { continue; } conflicts.add(conflictDescription); } } if (dryRun) { return SVNMergeResult.createMergeResult(status, null); } log = log == null ? adminArea.getLog() : log; adminArea.installProperties(localPath, baseProperties, workingProperties, log, baseMerge, true); if (!conflicts.isEmpty()) { SVNEntry entry = adminArea.getVersionedEntry(localPath, false); String prejTmpPath = adminArea.getThisDirName().equals(localPath) ? "tmp/dir_conflicts" : "tmp/props/" + localPath; File prejTmpFile = SVNFileUtil.createUniqueFile(adminArea.getAdminDirectory(), prejTmpPath, ".prej", false); prejTmpPath = SVNFileUtil.getBasePath(prejTmpFile); String prejPath = entry.getPropRejectFile(); if (prejPath == null) { prejPath = adminArea.getThisDirName().equals(localPath) ? "dir_conflicts" : localPath; File prejFile = SVNFileUtil.createUniqueFile(adminArea.getRoot(), prejPath, ".prej", false); prejPath = SVNFileUtil.getBasePath(prejFile); } File file = adminArea.getFile(prejTmpPath); OutputStream os = SVNFileUtil.openFileForWriting(file); try { for (Iterator lines = conflicts.iterator(); lines.hasNext();) { String line = (String) lines.next(); os.write(SVNEncodingUtil.fuzzyEscape(line).getBytes("UTF-8")); } os.write(SVNEncodingUtil.fuzzyEscape("\n").getBytes("UTF-8")); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot write properties conflict file: {1}", e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(os); } SVNProperties command = new SVNProperties(); command.put(SVNLog.NAME_ATTR, prejTmpPath); command.put(SVNLog.DEST_ATTR, prejPath); log.addCommand(SVNLog.APPEND, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, prejTmpPath); log.addCommand(SVNLog.DELETE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, localPath); command.put(SVNProperty.shortPropertyName(SVNProperty.PROP_REJECT_FILE), prejPath); log.addCommand(SVNLog.MODIFY_ENTRY, command, false); } return SVNMergeResult.createMergeResult(status, null); } public SVNDiffConflictChoiceStyle getDiffConflictStyle() { return myDiffConflictStyle; } public void setDiffConflictStyle(SVNDiffConflictChoiceStyle diffConflictStyle) { myDiffConflictStyle = diffConflictStyle; } protected SVNStatusType mergeBinary(File baseFile, File localFile, File repositoryFile, SVNDiffOptions options, File resultFile) throws SVNException { return SVNStatusType.CONFLICTED; } protected SVNStatusType mergeText(File baseFile, File localFile, File latestFile, SVNDiffOptions options, File resultFile) throws SVNException { FSMergerBySequence merger = new FSMergerBySequence(getConflictStartMarker(), getConflictSeparatorMarker(), getConflictEndMarker()); int mergeResult = 0; RandomAccessFile localIS = null; RandomAccessFile latestIS = null; RandomAccessFile baseIS = null; OutputStream result = null; try { result = SVNFileUtil.openFileForWriting(resultFile); localIS = new RandomAccessFile(localFile, "r"); latestIS = new RandomAccessFile(latestFile, "r"); baseIS = new RandomAccessFile(baseFile, "r"); QSequenceLineRAData baseData = new QSequenceLineRAFileData(baseIS); QSequenceLineRAData localData = new QSequenceLineRAFileData(localIS); QSequenceLineRAData latestData = new QSequenceLineRAFileData(latestIS); mergeResult = merger.merge(baseData, localData, latestData, options, result, getDiffConflictStyle()); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(result); SVNFileUtil.closeFile(localIS); SVNFileUtil.closeFile(baseIS); SVNFileUtil.closeFile(latestIS); } SVNStatusType status = SVNStatusType.UNCHANGED; if (mergeResult == FSMergerBySequence.CONFLICTED) { status = SVNStatusType.CONFLICTED; } else if (mergeResult == FSMergerBySequence.MERGED) { status = SVNStatusType.MERGED; } return status; } protected SVNMergeResult processMergedFiles(SVNMergeFileSet files, SVNMergeResult mergeResult) throws SVNException { DefaultSVNMergerAction mergeAction = getMergeAction(files, mergeResult); if (mergeAction == DefaultSVNMergerAction.MARK_CONFLICTED) { mergeResult = handleMarkConflicted(files); } else if (mergeAction == DefaultSVNMergerAction.CHOOSE_BASE) { mergeResult = handleChooseBase(files); } else if (mergeAction == DefaultSVNMergerAction.CHOOSE_REPOSITORY) { mergeResult = handleChooseRepository(files); } else if (mergeAction == DefaultSVNMergerAction.CHOOSE_WORKING) { mergeResult = handleChooseWorking(files); } else if (mergeAction == DefaultSVNMergerAction.CHOOSE_MERGED_FILE) { mergeResult = handleChooseMerged(files, mergeResult); } else if (mergeAction == DefaultSVNMergerAction.MARK_RESOLVED) { mergeResult = handleMarkResolved(files, mergeResult); } else if (mergeAction == DefaultSVNMergerAction.CHOOSE_REPOSITORY_CONFLICTED) { mergeResult = handleChooseConflicted(false, files); } else if (mergeAction == DefaultSVNMergerAction.CHOOSE_WORKING_CONFLICTED) { mergeResult = handleChooseConflicted(true, files); } postMergeCleanup(files); return mergeResult; } protected DefaultSVNMergerAction getMergeAction(SVNMergeFileSet files, SVNMergeResult mergeResult) throws SVNException { if (mergeResult.getMergeStatus() == SVNStatusType.CONFLICTED) { if (myConflictCallback != null) { SVNConflictDescription descr = new SVNTextConflictDescription(files, SVNNodeKind.FILE, SVNConflictAction.EDIT, SVNConflictReason.EDITED); SVNConflictResult result = myConflictCallback.handleConflict(descr); if (result == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CONFLICT_RESOLVER_FAILURE, "Conflict callback violated API: returned no results."); SVNErrorManager.error(err, SVNLogType.DEFAULT); } if (result.isIsSaveMerged()) { File mergedFile = result.getMergedFile() != null ? result.getMergedFile() : files.getResultFile(); File mergeTarget = files.getWCFile(); File editedMergedFile = SVNFileUtil.createUniqueFile(mergeTarget.getParentFile(), mergeTarget.getName(), ".edited", false); SVNLog log = files.getLog(); SVNProperties command = new SVNProperties(); command.put(SVNLog.NAME_ATTR, SVNFileUtil.getBasePath(mergedFile)); command.put(SVNLog.DEST_ATTR, editedMergedFile.getName()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } SVNConflictChoice choice = result.getConflictChoice(); if (choice == SVNConflictChoice.BASE) { return DefaultSVNMergerAction.CHOOSE_BASE; } else if (choice == SVNConflictChoice.MERGED) { return DefaultSVNMergerAction.CHOOSE_MERGED_FILE; } else if (choice == SVNConflictChoice.MINE_FULL) { return DefaultSVNMergerAction.CHOOSE_WORKING; } else if (choice == SVNConflictChoice.THEIRS_FULL) { return DefaultSVNMergerAction.CHOOSE_REPOSITORY; } else if (choice == SVNConflictChoice.MINE_CONFLICT) { return DefaultSVNMergerAction.CHOOSE_WORKING_CONFLICTED; } else if (choice == SVNConflictChoice.THEIRS_CONFLICT) { return DefaultSVNMergerAction.CHOOSE_REPOSITORY_CONFLICTED; } } return DefaultSVNMergerAction.MARK_CONFLICTED; } return DefaultSVNMergerAction.CHOOSE_MERGED_FILE; } protected SVNMergeResult handleChooseBase(SVNMergeFileSet files) throws SVNException { SVNLog log = files.getLog(); if (log != null) { SVNProperties command = new SVNProperties(); command.put(SVNLog.NAME_ATTR, files.getBasePath()); command.put(SVNLog.DEST_ATTR, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } return SVNMergeResult.createMergeResult(SVNStatusType.MERGED, null); } protected SVNMergeResult handleChooseRepository(SVNMergeFileSet files) throws SVNException { SVNLog log = files.getLog(); if (log != null) { SVNProperties command = new SVNProperties(); command.put(SVNLog.NAME_ATTR, files.getRepositoryPath()); command.put(SVNLog.DEST_ATTR, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } return SVNMergeResult.createMergeResult(SVNStatusType.MERGED, null); } protected SVNMergeResult handleChooseConflicted(boolean chooseMine, SVNMergeFileSet files) throws SVNException { File tmpFile = SVNAdminUtil.createTmpFile(files.getAdminArea()); setDiffConflictStyle(chooseMine ? SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED : SVNDiffConflictChoiceStyle.CHOOSE_LATEST); File leftFile = files.getBaseFile(); File rightFile = files.getRepositoryFile(); File detranslatedTarget = files.getLocalFile(); mergeText(leftFile, detranslatedTarget, rightFile, getDiffOptions(), tmpFile); SVNLog log = files.getLog(); if (log != null) { SVNProperties command = new SVNProperties(); String tmpBasePath = SVNFileUtil.getBasePath(tmpFile); command.put(SVNLog.NAME_ATTR, tmpBasePath); command.put(SVNLog.DEST_ATTR, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, tmpBasePath); log.addCommand(SVNLog.DELETE, command, false); command.clear(); } return SVNMergeResult.createMergeResult(SVNStatusType.MERGED, null); } protected SVNMergeResult handleChooseWorking(SVNMergeFileSet files) throws SVNException { if (files == null) { SVNErrorManager.cancel("", SVNLogType.WC); } return SVNMergeResult.createMergeResult(SVNStatusType.MERGED, null); } protected SVNMergeResult handleMarkConflicted(SVNMergeFileSet files) throws SVNException { if (files.isBinary()) { return handleMarkBinaryConflicted(files); } return handleMarkTextConflicted(files); } protected SVNMergeResult handleMarkBinaryConflicted(SVNMergeFileSet files) throws SVNException { SVNProperties command = new SVNProperties(); File root = files.getAdminArea().getRoot(); SVNLog log = files.getLog(); File oldFile = SVNFileUtil.createUniqueFile(root, files.getWCPath(), files.getBaseLabel(), false); File newFile = SVNFileUtil.createUniqueFile(root, files.getWCPath(), files.getRepositoryLabel(), false); SVNFileUtil.copyFile(files.getBaseFile(), oldFile, false); SVNFileUtil.copyFile(files.getRepositoryFile(), newFile, false); if (!files.getLocalPath().equals(files.getWCPath())) { File mineFile = SVNFileUtil.createUniqueFile(root, files.getWCPath(), files.getLocalLabel(), false); String minePath = SVNFileUtil.getBasePath(mineFile); if (log != null) { command.put(SVNLog.NAME_ATTR, files.getLocalPath()); command.put(SVNLog.DEST_ATTR, minePath); log.addCommand(SVNLog.MOVE, command, false); command.clear(); } command.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_WRK), minePath); } else { command.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_WRK), ""); } String newPath = SVNFileUtil.getBasePath(newFile); String oldPath = SVNFileUtil.getBasePath(oldFile); makeBinaryConflictEntry(files, newPath, oldPath); return SVNMergeResult.createMergeResult(SVNStatusType.CONFLICTED, null); } protected void makeBinaryConflictEntry(SVNMergeFileSet files, String newFilePath, String oldFilePath) throws SVNException { SVNProperties command = new SVNProperties(); SVNLog log = files.getLog(); if (log != null) { command.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_NEW), newFilePath); command.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_OLD), oldFilePath); log.logChangedEntryProperties(files.getWCPath(), command); command.clear(); } files.getAdminArea().saveEntries(false); } protected SVNMergeResult handleMarkTextConflicted(SVNMergeFileSet files) throws SVNException { SVNProperties command = new SVNProperties(); File root = files.getAdminArea().getRoot(); SVNLog log = files.getLog(); if (files.getCopyFromFile() != null) { String copyFromPath = files.getCopyFromPath(); String detranslatedPath = files.getWCPath(); if (log != null) { command.put(SVNLog.NAME_ATTR, copyFromPath); command.put(SVNLog.DEST_ATTR, detranslatedPath); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } } File mineFile = SVNFileUtil.createUniqueFile(root, files.getWCPath(), files.getLocalLabel(), false); File oldFile = SVNFileUtil.createUniqueFile(root, files.getWCPath(), files.getBaseLabel(), false); File newFile = SVNFileUtil.createUniqueFile(root, files.getWCPath(), files.getRepositoryLabel(), false); String newPath = SVNFileUtil.getBasePath(newFile); String oldPath = SVNFileUtil.getBasePath(oldFile); String minePath = SVNFileUtil.getBasePath(mineFile); String basePath = files.getBasePath(); String latestPath = files.getRepositoryPath(); File tmpTargetCopy = SVNTranslator.getTranslatedFile(files.getAdminArea(), files.getWCPath(), files.getWCFile(), false, false, false, true); String tmpTargetCopyPath = SVNFileUtil.getBasePath(tmpTargetCopy); if (log != null) { command.put(SVNLog.NAME_ATTR, basePath); command.put(SVNLog.DEST_ATTR, oldPath); command.put(SVNLog.ATTR2, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, latestPath); command.put(SVNLog.DEST_ATTR, newPath); command.put(SVNLog.ATTR2, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, tmpTargetCopyPath); command.put(SVNLog.DEST_ATTR, minePath); command.put(SVNLog.ATTR2, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); if (!tmpTargetCopy.equals(files.getLocalFile())) { command.put(SVNLog.NAME_ATTR, tmpTargetCopyPath); log.addCommand(SVNLog.DELETE, command, false); command.clear(); } command.put(SVNLog.NAME_ATTR, files.getResultPath()); command.put(SVNLog.DEST_ATTR, files.getWCPath()); command.put(SVNLog.ATTR2, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } makeTextConflictEntry(files, minePath, newPath, oldPath); return SVNMergeResult.createMergeResult(SVNStatusType.CONFLICTED, null); } protected void makeTextConflictEntry(SVNMergeFileSet files, String mineFilePath, String newFilePath, String oldFilePath) throws SVNException { SVNLog log = files.getLog(); if (log != null) { SVNProperties command = new SVNProperties(); command.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_WRK), mineFilePath); command.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_NEW), newFilePath); command.put(SVNProperty.shortPropertyName(SVNProperty.CONFLICT_OLD), oldFilePath); log.logChangedEntryProperties(files.getWCPath(), command); command.clear(); } } protected SVNMergeResult handleChooseMerged(SVNMergeFileSet files, SVNMergeResult mergeResult) throws SVNException { SVNProperties command = new SVNProperties(); SVNLog log = files.getLog(); if (mergeResult.getMergeStatus() != SVNStatusType.CONFLICTED) { // do normal merge. if (mergeResult.getMergeStatus() != SVNStatusType.UNCHANGED) { if (log != null) { command.put(SVNLog.NAME_ATTR, files.getResultPath()); command.put(SVNLog.DEST_ATTR, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } } return mergeResult; } else if (files.isBinary()) { // this action is not applicable for binary conflited files. return handleMarkConflicted(files); } else { if (log != null) { // for text file we could use merged version in case of conflict. command.put(SVNLog.NAME_ATTR, files.getResultPath()); command.put(SVNLog.DEST_ATTR, files.getWCPath()); log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false); command.clear(); } return SVNMergeResult.createMergeResult(SVNStatusType.MERGED, null); } } protected SVNMergeResult handleMarkResolved(SVNMergeFileSet files, SVNMergeResult mergeResult) throws SVNException { if (!files.isBinary()) { // same as choose merged. return handleChooseMerged(files, mergeResult); } // same as choose working. return handleChooseWorking(files); } protected void postMergeCleanup(SVNMergeFileSet files) throws SVNException { SVNProperties command = new SVNProperties(); SVNLog log = files.getLog(); if (!files.getLocalPath().equals(files.getWCPath())) { if (log != null) { command.put(SVNLog.NAME_ATTR, files.getLocalPath()); log.addCommand(SVNLog.DELETE, command, false); command.clear(); } } if (log != null) { command.put(SVNLog.NAME_ATTR, files.getWCPath()); log.addCommand(SVNLog.MAYBE_EXECUTABLE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, files.getWCPath()); log.addCommand(SVNLog.MAYBE_READONLY, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, files.getResultPath()); log.addCommand(SVNLog.DELETE, command, false); command.clear(); } } private SVNStatusType applySinglePropertyAdd(String localPath, boolean isDir, SVNStatusType status, SVNProperties workingProps, String propName, SVNPropertyValue baseValue, SVNPropertyValue newValue, SVNPropertyValue workingValue, SVNAdminArea adminArea, SVNLog log, Collection conflicts, boolean dryRun) throws SVNException { boolean gotConflict = false; if (workingValue != null) { if (workingValue.equals(newValue)) { status = getPropMergeStatus(status, SVNStatusType.MERGED); changeProperty(workingProps, propName, newValue); } else { if (SVNProperty.MERGE_INFO.equals(propName)) { newValue = SVNPropertyValue.create( SVNMergeInfoUtil.combineMergeInfoProperties(workingValue.getString(), newValue.getString())); changeProperty(workingProps, propName, newValue); status = getPropMergeStatus(status, SVNStatusType.MERGED); } else { gotConflict = maybeGeneratePropConflict(localPath, propName, workingProps, null, newValue, baseValue, workingValue, adminArea, log, isDir, dryRun); if (gotConflict) { conflicts.add(MessageFormat.format("Trying to add new property ''{0}'' with value ''{1}'',\n" + "but property already exists with value ''{2}''.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(newValue), SVNPropertyValue.getPropertyAsString(workingValue) })); } } } } else if (baseValue != null) { gotConflict = maybeGeneratePropConflict(localPath, propName, workingProps, null, newValue, baseValue, null, adminArea, log, isDir, dryRun); if (gotConflict) { conflicts.add(MessageFormat.format("Trying to create property ''{0}'' with value ''{1}'',\n" + "but it has been locally deleted.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(newValue) })); } } else { changeProperty(workingProps, propName, newValue); } return status; } private void changeProperty(SVNProperties properties, String propName, SVNPropertyValue propValue) { if (propValue == null) { properties.remove(propName); } else { properties.put(propName, propValue); } } private SVNStatusType applySinglePropertyChange(String localPath, boolean isDir, SVNStatusType status, SVNProperties workingProps, String propName, SVNPropertyValue baseValue, SVNPropertyValue oldValue, SVNPropertyValue newValue, SVNPropertyValue workingValue, SVNAdminArea adminArea, SVNLog log, Collection conflicts, boolean dryRun) throws SVNException { if (SVNProperty.MERGE_INFO.equals(propName)) { return applySingleMergeInfoPropertyChange(localPath, isDir, status, workingProps, propName, baseValue, oldValue, newValue, workingValue, adminArea, log, conflicts, dryRun); } return applySingleGenericPropertyChange(localPath, isDir, status, workingProps, propName, baseValue, oldValue, newValue, workingValue, adminArea, log, conflicts, dryRun); } private SVNStatusType applySingleMergeInfoPropertyChange(String localPath, boolean isDir, SVNStatusType status, SVNProperties workingProps, String propName, SVNPropertyValue baseValue, SVNPropertyValue oldValue, SVNPropertyValue newValue, SVNPropertyValue workingValue, SVNAdminArea adminArea, SVNLog log, Collection conflicts, boolean dryRun) throws SVNException { boolean gotConflict = false; if ((workingValue != null && baseValue == null) || (workingValue == null && baseValue != null) || (workingValue != null && baseValue != null && !workingValue.equals(baseValue))) { if (workingValue != null) { if (workingValue.equals(newValue)) { status = getPropMergeStatus(status, SVNStatusType.MERGED); } else { newValue = SVNPropertyValue.create(SVNMergeInfoUtil.combineForkedMergeInfoProperties(oldValue.getString(), workingValue.getString(), newValue.getString())); changeProperty(workingProps, propName, newValue); status = getPropMergeStatus(status, SVNStatusType.MERGED); } } else { gotConflict = maybeGeneratePropConflict(localPath, propName, workingProps, oldValue, newValue, baseValue, workingValue, adminArea, log, isDir, dryRun); if (gotConflict) { conflicts.add(MessageFormat.format("Trying to change property ''{0}'' from ''{1}'' to ''{2}'',\n" + "but it has been locally deleted.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(oldValue), SVNPropertyValue.getPropertyAsString(newValue) })); } } } else if (workingValue == null) { Map addedMergeInfo = new TreeMap(); SVNMergeInfoUtil.diffMergeInfoProperties(null, addedMergeInfo, oldValue.getString(), null, newValue.getString(), null); newValue = SVNPropertyValue.create(SVNMergeInfoUtil.formatMergeInfoToString(addedMergeInfo, null)); changeProperty(workingProps, propName, newValue); } else { if (baseValue.equals(oldValue)) { changeProperty(workingProps, propName, newValue); } else { newValue = SVNPropertyValue.create(SVNMergeInfoUtil.combineForkedMergeInfoProperties(oldValue.getString(), workingValue.getString(), newValue.getString())); changeProperty(workingProps, propName, newValue); status = getPropMergeStatus(status, SVNStatusType.MERGED); } } return status; } private SVNStatusType applySingleGenericPropertyChange(String localPath, boolean isDir, SVNStatusType status, SVNProperties workingProps, String propName, SVNPropertyValue baseValue, SVNPropertyValue oldValue, SVNPropertyValue newValue, SVNPropertyValue workingValue, SVNAdminArea adminArea, SVNLog log, Collection conflicts, boolean dryRun) throws SVNException { boolean gotConflict = false; if ((workingValue == null && oldValue == null) || (workingValue != null && oldValue != null && workingValue.equals(oldValue))) { changeProperty(workingProps, propName, newValue); } else { gotConflict = maybeGeneratePropConflict(localPath, propName, workingProps, oldValue, newValue, baseValue, workingValue, adminArea, log, isDir, dryRun); if (gotConflict) { if (workingValue != null && baseValue != null && workingValue.equals(baseValue)) { conflicts.add(MessageFormat.format("Trying to change property ''{0}'' from ''{1}'' to ''{2}'',\n" + "but property already exists with value ''{3}''.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(oldValue), SVNPropertyValue.getPropertyAsString(newValue), SVNPropertyValue.getPropertyAsString(workingValue) })); } else if (workingValue != null && baseValue != null) { conflicts.add(MessageFormat.format("Trying to change property ''{0}'' from ''{1}'' to ''{2}'',\n" + "but the property has been locally changed from ''{3}'' to ''{4}''.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(oldValue), SVNPropertyValue.getPropertyAsString(newValue), SVNPropertyValue.getPropertyAsString(baseValue), SVNPropertyValue.getPropertyAsString(workingValue) })); } else if (workingValue != null) { conflicts.add(MessageFormat.format("Trying to change property ''{0}'' from ''{1}'' to ''{2}'',\n" + "but property has been locally added with value ''{3}''.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(oldValue), SVNPropertyValue.getPropertyAsString(newValue), SVNPropertyValue.getPropertyAsString(workingValue) })); } else if (baseValue != null) { conflicts.add(MessageFormat.format("Trying to change property ''{0}'' from ''{1}'' to ''{2}'',\n" + "but it has been locally deleted.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(oldValue), SVNPropertyValue.getPropertyAsString(newValue) })); } else { conflicts.add(MessageFormat.format("Trying to change property ''{0}'' from ''{1}'' to ''{2}'',\n" + "but the property does not exist.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(oldValue), SVNPropertyValue.getPropertyAsString(newValue) })); } } } return status; } private SVNStatusType applySinglePropertyDelete(String localPath, boolean isDir, SVNStatusType status, SVNProperties workingProps, String propName, SVNPropertyValue baseValue, SVNPropertyValue oldValue, SVNPropertyValue workingValue, SVNAdminArea adminArea, SVNLog log, Collection conflicts, boolean dryRun) throws SVNException { boolean gotConflict = false; if (baseValue == null) { changeProperty(workingProps, propName, (SVNPropertyValue) null); if (oldValue != null) { status = getPropMergeStatus(status, SVNStatusType.MERGED); } } else if (baseValue.equals(oldValue)) { if (workingValue != null) { if (workingValue.equals(oldValue)) { changeProperty(workingProps, propName, (SVNPropertyValue) null); } else { gotConflict = maybeGeneratePropConflict(localPath, propName, workingProps, oldValue, null, baseValue, workingValue, adminArea, log, isDir, dryRun); if (gotConflict) { conflicts.add(MessageFormat.format("Trying to delete property ''{0}'' with value ''{1}''\n " + "but it has been modified from ''{2}'' to ''{3}''.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(oldValue), SVNPropertyValue.getPropertyAsString(baseValue), SVNPropertyValue.getPropertyAsString(workingValue) })); } } } else { status = getPropMergeStatus(status, SVNStatusType.MERGED); } } else { gotConflict = maybeGeneratePropConflict(localPath, propName, workingProps, oldValue, null, baseValue, workingValue, adminArea, log, isDir, dryRun); if (gotConflict) { conflicts.add(MessageFormat.format("Trying to delete property ''{0}'' with value ''{1}''\n " + "but the local value is ''{2}''.", new Object[] { propName, SVNPropertyValue.getPropertyAsString(baseValue), SVNPropertyValue.getPropertyAsString(workingValue) })); } } return status; } private static SVNStatusType getPropMergeStatus(SVNStatusType status, SVNStatusType newStatus) { if (status == null) { return null; } int statusInd = STATUS_ORDERING.indexOf(status); int newStatusInd = STATUS_ORDERING.indexOf(newStatus); if (newStatusInd <= statusInd) { return status; } return newStatus; } private boolean maybeGeneratePropConflict(String localPath, String propName, SVNProperties workingProps, SVNPropertyValue oldValue, SVNPropertyValue newValue, SVNPropertyValue baseValue, SVNPropertyValue workingValue, SVNAdminArea adminArea, SVNLog log, boolean isDir, boolean dryRun) throws SVNException { boolean conflictRemains = true; if (myConflictCallback == null || dryRun) { return conflictRemains; } File path = adminArea.getFile(localPath); File workingFile = null; File newFile = null; File baseFile = null; File mergedFile = null; try { if (workingValue != null) { workingFile = SVNFileUtil.createUniqueFile(path.getParentFile(), path.getName(), ".tmp", false); OutputStream os = SVNFileUtil.openFileForWriting(workingFile); try { os.write(SVNPropertyValue.getPropertyAsBytes(workingValue)); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot write a working property value file: {1}", e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(os); } } if (newValue != null) { newFile = SVNFileUtil.createUniqueFile(path.getParentFile(), path.getName(), ".tmp", false); OutputStream os = SVNFileUtil.openFileForWriting(newFile); try { os.write(SVNPropertyValue.getPropertyAsBytes(newValue)); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot write a new property value file: {1}", e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(os); } } if ((baseValue != null && oldValue == null) || (baseValue == null && oldValue != null)) { SVNPropertyValue theValue = baseValue != null ? baseValue : oldValue; baseFile = SVNFileUtil.createUniqueFile(path.getParentFile(), path.getName(), ".tmp", false); OutputStream os = SVNFileUtil.openFileForWriting(baseFile); try { os.write(SVNPropertyValue.getPropertyAsBytes(theValue)); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot write a base property value file: {1}", e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(os); } } else if (baseValue != null && oldValue != null) { SVNPropertyValue theValue = baseValue; if (!baseValue.equals(oldValue)) { if (workingValue != null && baseValue.equals(workingValue)) { theValue = oldValue; } } baseFile = SVNFileUtil.createUniqueFile(path.getParentFile(), path.getName(), ".tmp", false); OutputStream os = SVNFileUtil.openFileForWriting(baseFile); try { os.write(SVNPropertyValue.getPropertyAsBytes(theValue)); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot write a base property value file: {1}", e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(os); } if (workingValue != null && newValue != null) { FSMergerBySequence merger = new FSMergerBySequence(getConflictStartMarker(), getConflictSeparatorMarker(), getConflictEndMarker()); OutputStream result = null; try { mergedFile = SVNFileUtil.createUniqueFile(path.getParentFile(), path.getName(), ".tmp", false); result = SVNFileUtil.openFileForWriting(mergedFile); QSequenceLineRAData baseData = new QSequenceLineRAByteData(SVNPropertyValue.getPropertyAsBytes(theValue)); QSequenceLineRAData localData = new QSequenceLineRAByteData(SVNPropertyValue.getPropertyAsBytes(workingValue)); QSequenceLineRAData latestData = new QSequenceLineRAByteData(SVNPropertyValue.getPropertyAsBytes(newValue)); merger.merge(baseData, localData, latestData, null, result, null); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(result); } } } String mimeType = null; if (!isDir && workingProps != null) { mimeType = workingProps.getStringValue(SVNProperty.MIME_TYPE); } SVNMergeFileSet fileSet = new SVNMergeFileSet(adminArea, log, baseFile, workingFile, localPath, newFile, mergedFile, null, mimeType); SVNConflictAction action = SVNConflictAction.EDIT; if (oldValue == null && newValue != null) { action = SVNConflictAction.ADD; } else if (oldValue != null && newValue == null) { action = SVNConflictAction.DELETE; } SVNConflictReason reason = SVNConflictReason.EDITED; if (baseValue != null && workingValue == null) { reason = SVNConflictReason.DELETED; } else if (baseValue == null && workingValue != null) { reason = SVNConflictReason.OBSTRUCTED; } SVNConflictDescription description = new SVNPropertyConflictDescription(fileSet, isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, propName, action, reason); SVNConflictResult result = myConflictCallback.handleConflict(description); if (result == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CONFLICT_RESOLVER_FAILURE, "Conflict callback violated API: returned no results."); SVNErrorManager.error(err, SVNLogType.DEFAULT); } SVNConflictChoice choice = result.getConflictChoice(); if (choice == SVNConflictChoice.MINE_FULL) { conflictRemains = false; } else if (choice == SVNConflictChoice.THEIRS_FULL) { changeProperty(workingProps, propName, newValue); conflictRemains = false; } else if (choice == SVNConflictChoice.BASE) { changeProperty(workingProps, propName, baseValue); conflictRemains = false; } else if (choice == SVNConflictChoice.MERGED) { if (mergedFile == null && result.getMergedFile() == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CONFLICT_RESOLVER_FAILURE, "Conflict callback violated API: returned no merged file."); SVNErrorManager.error(err, SVNLogType.DEFAULT); } String mergedString = SVNFileUtil.readFile(mergedFile != null ? mergedFile : result.getMergedFile()); changeProperty(workingProps, propName, SVNPropertyValue.create(mergedString)); conflictRemains = false; } return conflictRemains; } finally { SVNFileUtil.deleteFile(workingFile); SVNFileUtil.deleteFile(newFile); SVNFileUtil.deleteFile(baseFile); SVNFileUtil.deleteFile(mergedFile); } } }