/* * ==================================================================== * 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.cli.svn; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.text.MessageFormat; import org.tmatesoft.svn.cli.SVNCommandUtil; 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.internal.wc.FSMergerBySequence; import org.tmatesoft.svn.core.internal.wc.SVNDiffConflictChoiceStyle; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.wc.ISVNConflictHandler; 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.util.SVNLogType; 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 SVNCommandLineConflictHandler implements ISVNConflictHandler { private SVNConflictAcceptPolicy myAccept; private SVNCommandEnvironment mySVNEnvironment; private boolean myIsExternalFailed; public SVNCommandLineConflictHandler(SVNConflictAcceptPolicy accept, SVNCommandEnvironment environment) { myAccept = accept; mySVNEnvironment = environment; } public SVNConflictResult handleConflict(SVNConflictDescription conflictDescription) throws SVNException { if (conflictDescription.isTreeConflict()) { return null; } SVNMergeFileSet files = conflictDescription.getMergeFiles(); if (myAccept == SVNConflictAcceptPolicy.POSTPONE) { return new SVNConflictResult(SVNConflictChoice.POSTPONE, null); } else if (myAccept == SVNConflictAcceptPolicy.BASE) { return new SVNConflictResult(SVNConflictChoice.BASE, null); } else if (myAccept == SVNConflictAcceptPolicy.WORKING) { return new SVNConflictResult(SVNConflictChoice.MERGED, null); } else if (myAccept == SVNConflictAcceptPolicy.MINE_CONFLICT) { return new SVNConflictResult(SVNConflictChoice.MINE_CONFLICT, null); } else if (myAccept == SVNConflictAcceptPolicy.THEIRS_CONFLICT) { return new SVNConflictResult(SVNConflictChoice.THEIRS_CONFLICT, null); } else if (myAccept == SVNConflictAcceptPolicy.MINE_FULL) { return new SVNConflictResult(SVNConflictChoice.MINE_FULL, null); } else if (myAccept == SVNConflictAcceptPolicy.THEIRS_FULL) { return new SVNConflictResult(SVNConflictChoice.THEIRS_FULL, null); } else if (myAccept == SVNConflictAcceptPolicy.EDIT) { if (files.getResultFile() != null) { if (myIsExternalFailed) { return new SVNConflictResult(SVNConflictChoice.POSTPONE, null); } try { SVNCommandUtil.editFileExternally(mySVNEnvironment, mySVNEnvironment.getEditorCommand(), files.getResultFile().getAbsolutePath()); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.CL_NO_EXTERNAL_EDITOR) { mySVNEnvironment.getErr().println(svne.getErrorMessage().getMessage() != null ? svne.getErrorMessage().getMessage() : "No editor found, leaving all conflicts."); myIsExternalFailed = true; } else if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.EXTERNAL_PROGRAM) { String message = svne.getErrorMessage().getMessageTemplate() != null ? svne.getErrorMessage().getMessage() : "Error running editor, leaving all conflicts."; if (message.startsWith("svn: ")) { //hack: use the original message template without any prefixes (like 'svn:', 'svn:warning') //added to make update test 42 pass message = message.substring("svn: ".length()); } mySVNEnvironment.getErr().println(message); myIsExternalFailed = true; } else { throw svne; } } return new SVNConflictResult(SVNConflictChoice.MERGED, null); } } else if (myAccept == SVNConflictAcceptPolicy.LAUNCH) { if (files.getBaseFile() != null && files.getLocalFile() != null && files.getRepositoryFile() != null && files.getResultFile() != null) { if (myIsExternalFailed) { return new SVNConflictResult(SVNConflictChoice.POSTPONE, null); } boolean[] remainsInConflict = { false }; try { SVNCommandUtil.mergeFileExternally(mySVNEnvironment, files.getBaseFile().getAbsolutePath(), files.getRepositoryFile().getAbsolutePath(), files.getLocalFile().getAbsolutePath(), files.getResultFile().getAbsolutePath(), files.getWCPath(), remainsInConflict); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.CL_NO_EXTERNAL_MERGE_TOOL) { mySVNEnvironment.getErr().println(svne.getErrorMessage().getMessage() != null ? svne.getErrorMessage().getMessage() : "No merge tool found."); myIsExternalFailed = true; } else if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.EXTERNAL_PROGRAM) { mySVNEnvironment.getErr().println(svne.getErrorMessage().getMessage() != null ? svne.getErrorMessage().getMessage() : "Error running merge tool."); myIsExternalFailed = true; } throw svne; } if (remainsInConflict[0]) { return new SVNConflictResult(SVNConflictChoice.POSTPONE, null); } return new SVNConflictResult(SVNConflictChoice.MERGED, null); } } boolean saveMerged = false; SVNConflictChoice choice = SVNConflictChoice.POSTPONE; if ((conflictDescription.getNodeKind() == SVNNodeKind.FILE && conflictDescription.getConflictAction() == SVNConflictAction.EDIT && conflictDescription.getConflictReason() == SVNConflictReason.EDITED) || conflictDescription.isPropertyConflict()) { boolean performedEdit = false; boolean diffAllowed = false; boolean knowsSmth = false; String path = mySVNEnvironment.getRelativePath(files.getWCFile()); path = SVNCommandUtil.getLocalPath(path); if (conflictDescription.isPropertyConflict()) { String message = "Conflict for property ''{0}'' discovered on ''{1}''."; message = MessageFormat.format(message, new Object[] { conflictDescription.getPropertyName(), path }); mySVNEnvironment.getErr().println(message); if ((files.getLocalFile() == null && files.getRepositoryFile() != null) || (files.getLocalFile() != null && files.getRepositoryFile() == null)) { if (files.getLocalFile() != null) { String myVal = SVNFileUtil.readFile(files.getLocalFile()); message = MessageFormat.format("They want to delete the property, you want to change the value to ''{0}''.", new Object[] { myVal }); mySVNEnvironment.getErr().println(message); } else { String reposVal = SVNFileUtil.readFile(files.getRepositoryFile()); message = MessageFormat.format("They want to change the property value to ''{0}'', you want to delete the property.", new Object[] { reposVal }); mySVNEnvironment.getErr().println(message); } } } else { String message = "Conflict discovered in ''{0}''."; message = MessageFormat.format(message, new Object[] { path }); mySVNEnvironment.getErr().println(message); } if ((files.getResultFile() != null && files.getBaseFile() != null) || (files.getBaseFile() != null && files.getLocalFile() != null && files.getRepositoryFile() != null)) { diffAllowed = true; } while (true) { String message = "Select: (p) postpone"; if (diffAllowed) { message += ", (df) diff-full, (e) edit"; if (knowsSmth) { message += ", (r) resolved"; } if (!files.isBinary() && !conflictDescription.isPropertyConflict()) { message += ",\n (mc) mine-conflict, (tc) theirs-conflict"; } } else { if (knowsSmth) { message += ", (r) resolved"; } message += ",\n (mf) mine-full, (tf) theirs-full"; } message += ",\n (s) show all options: "; String answer = SVNCommandUtil.prompt(message, mySVNEnvironment); if ("s".equals(answer)) { mySVNEnvironment.getErr().println(); mySVNEnvironment.getErr().println(" (e) edit - change merged file in an editor"); mySVNEnvironment.getErr().println(" (df) diff-full - show all changes made to merged file"); mySVNEnvironment.getErr().println(" (r) resolved - accept merged version of file"); mySVNEnvironment.getErr().println(); mySVNEnvironment.getErr().println(" (dc) display-conflict - show all conflicts (ignoring merged version)"); mySVNEnvironment.getErr().println(" (mc) mine-conflict - accept my version for all conflicts (same)"); mySVNEnvironment.getErr().println(" (tc) theirs-conflict - accept their version for all conflicts (same)"); mySVNEnvironment.getErr().println(); mySVNEnvironment.getErr().println(" (mf) mine-full - accept my version of entire file (even non-conflicts)"); mySVNEnvironment.getErr().println(" (tf) theirs-full - accept their version of entire file (same)"); mySVNEnvironment.getErr().println(); mySVNEnvironment.getErr().println(" (p) postpone - mark the conflict to be resolved later"); mySVNEnvironment.getErr().println(" (l) launch - launch external tool to resolve conflict"); mySVNEnvironment.getErr().println(" (s) show all - show this list"); mySVNEnvironment.getErr().println(); } else if ("p".equals(answer)) { choice = SVNConflictChoice.POSTPONE; break; } else if ("mc".equals(answer)) { if (files.isBinary()) { mySVNEnvironment.getErr().println("Invalid option; cannot choose based on conflicts in a binary file."); mySVNEnvironment.getErr().println(); continue; } else if (conflictDescription.isPropertyConflict()) { mySVNEnvironment.getErr().println("Invalid option; cannot choose based on conflicts for properties."); mySVNEnvironment.getErr().println(); continue; } choice = SVNConflictChoice.MINE_CONFLICT; if (performedEdit) { saveMerged = true; } break; } else if ("tc".equals(answer)) { if (files.isBinary()) { mySVNEnvironment.getErr().println("Invalid option; cannot choose based on conflicts in a binary file."); mySVNEnvironment.getErr().println(); continue; } else if (conflictDescription.isPropertyConflict()) { mySVNEnvironment.getErr().println("Invalid option; cannot choose based on conflicts for properties."); mySVNEnvironment.getErr().println(); continue; } choice = SVNConflictChoice.THEIRS_CONFLICT; if (performedEdit) { saveMerged = true; } break; } else if ("mf".equals(answer)) { choice = SVNConflictChoice.MINE_FULL; if (performedEdit) { saveMerged = true; } break; } else if ("tf".equals(answer)) { choice = SVNConflictChoice.THEIRS_FULL; if (performedEdit) { saveMerged = true; } break; } else if ("dc".equals(answer)) { if (files.isBinary()) { mySVNEnvironment.getErr().println("Invalid option; cannot display conflicts for a binary file."); mySVNEnvironment.getErr().println(); continue; } else if (conflictDescription.isPropertyConflict()) { mySVNEnvironment.getErr().println("Invalid option; cannot display conflicts for properties."); mySVNEnvironment.getErr().println(); continue; } else if (files.getLocalFile() == null || files.getBaseFile() == null || files.getRepositoryFile() == null) { mySVNEnvironment.getErr().println("Invalid option; original files not available."); mySVNEnvironment.getErr().println(); continue; } //TODO: re-implement in future showConflictedChunks(files); knowsSmth = true; continue; } else if ("df".equals(answer)) { if (!diffAllowed) { mySVNEnvironment.getErr().println("Invalid option; there's no merged version to diff."); mySVNEnvironment.getErr().println(); continue; } File path1 = null; File path2 = null; if (files.getResultFile() != null && files.getBaseFile() != null) { path1 = files.getBaseFile(); path2 = files.getResultFile(); } else { path1 = files.getRepositoryFile(); path2 = files.getLocalFile(); } DefaultSVNCommandLineDiffGenerator diffGenerator = new DefaultSVNCommandLineDiffGenerator(path1, path2); diffGenerator.setDiffOptions(new SVNDiffOptions(false, false, true)); diffGenerator.displayFileDiff("", path1, path2, null, null, null, null, System.out); knowsSmth = true; } else if ("e".equals(answer)) { if (files.getResultFile() != null) { try { String resultPath = files.getResultFile().getAbsolutePath(); SVNCommandUtil.editFileExternally(mySVNEnvironment, mySVNEnvironment.getEditorCommand(), resultPath); performedEdit = true; } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.CL_NO_EXTERNAL_EDITOR) { mySVNEnvironment.getErr().println(svne.getErrorMessage().getMessage() != null ? svne.getErrorMessage().getMessage() : "No editor found."); } else if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.EXTERNAL_PROGRAM) { mySVNEnvironment.getErr().println(svne.getErrorMessage().getMessage() != null ? svne.getErrorMessage().getMessage() : "Error running editor."); } else { throw svne; } } } else { mySVNEnvironment.getErr().println("Invalid option; there's no merged version to edit."); mySVNEnvironment.getErr().println(); } if (performedEdit) { knowsSmth = true; } } else if ("l".equals(answer)) { if (files.getBaseFile() != null && files.getLocalFile() != null && files.getRepositoryFile() != null && files.getResultFile() != null) { try { SVNCommandUtil.mergeFileExternally(mySVNEnvironment, files.getBasePath(), files.getRepositoryPath(), files.getLocalPath(), files.getResultPath(), files.getWCPath(), null); performedEdit = true; } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.CL_NO_EXTERNAL_MERGE_TOOL) { mySVNEnvironment.getErr().println(svne.getErrorMessage().getMessage() != null ? svne.getErrorMessage().getMessage() : "No merge tool found."); myIsExternalFailed = true; } else if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.EXTERNAL_PROGRAM) { mySVNEnvironment.getErr().println(svne.getErrorMessage().getMessage() != null ? svne.getErrorMessage().getMessage() : "Error running merge tool."); myIsExternalFailed = true; } else { throw svne; } } } else { mySVNEnvironment.getErr().println("Invalid option."); mySVNEnvironment.getErr().println(); } } else if ("r".equals(answer)) { if (knowsSmth) { choice = SVNConflictChoice.MERGED; break; } mySVNEnvironment.getErr().println("Invalid option."); mySVNEnvironment.getErr().println(); } } } else if (conflictDescription.getConflictAction() == SVNConflictAction.ADD && conflictDescription.getConflictReason() == SVNConflictReason.OBSTRUCTED) { String message = "Conflict discovered when trying to add ''{0}''."; message = MessageFormat.format(message, new Object[] { files.getWCFile() }); mySVNEnvironment.getErr().println(message); mySVNEnvironment.getErr().println("An object of the same name already exists."); String prompt = "Select: (p) postpone, (mf) mine-full, (tf) theirs-full, (h) help:"; while (true) { String answer = SVNCommandUtil.prompt(prompt, mySVNEnvironment); if ("h".equals(answer) || "?".equals(answer)) { mySVNEnvironment.getErr().println(" (p) postpone - resolve the conflict later"); mySVNEnvironment.getErr().println(" (mf) mine-full - accept pre-existing item (ignore upstream addition)"); mySVNEnvironment.getErr().println(" (tf) theirs-full - accept incoming item (overwrite pre-existing item)"); mySVNEnvironment.getErr().println(" (h) help - show this help"); mySVNEnvironment.getErr().println(); } if ("p".equals(answer)) { choice = SVNConflictChoice.POSTPONE; break; } if ("mf".equals(answer)) { choice = SVNConflictChoice.MINE_FULL; break; } if ("tf".equals(answer)) { choice = SVNConflictChoice.THEIRS_FULL; break; } } } else { choice = SVNConflictChoice.POSTPONE; } return new SVNConflictResult(choice, null, saveMerged); } private void showConflictedChunks(SVNMergeFileSet files) throws SVNException { byte[] conflictStartMarker = "<<<<<<< MINE (select with 'mc')".getBytes(); byte[] conflictSeparator = "=======".getBytes(); byte[] conflictEndMarker = ">>>>>>> THEIRS (select with 'tc')".getBytes(); byte[] conflictOriginalMarker = "||||||| ORIGINAL".getBytes(); SVNDiffOptions options = new SVNDiffOptions(false, false, true); FSMergerBySequence merger = new FSMergerBySequence(conflictStartMarker, conflictSeparator, conflictEndMarker, conflictOriginalMarker); RandomAccessFile localIS = null; RandomAccessFile latestIS = null; RandomAccessFile baseIS = null; try { localIS = new RandomAccessFile(files.getWCFile(), "r"); latestIS = new RandomAccessFile(files.getRepositoryFile(), "r"); baseIS = new RandomAccessFile(files.getBaseFile(), "r"); QSequenceLineRAData baseData = new QSequenceLineRAFileData(baseIS); QSequenceLineRAData localData = new QSequenceLineRAFileData(localIS); QSequenceLineRAData latestData = new QSequenceLineRAFileData(latestIS); merger.merge(baseData, localData, latestData, options, mySVNEnvironment.getOut(), SVNDiffConflictChoiceStyle.CHOOSE_ONLY_CONFLICTS); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(localIS); SVNFileUtil.closeFile(baseIS); SVNFileUtil.closeFile(latestIS); } } }