Performing 'svn merge URL_TO_TRUNK BRANCH_WC' and handling conflicts with SVNKit

This example is similar to Merging from trunk to a branch except for that the merge to the branch working copy produces a conflict since we make some changes to the same files in the branch working copy. In this example we demonstrate how you can handle conflicts programmatically using ISVNConflictHandler.

   1 /*
   2  * ====================================================================
   3  * Copyright (c) 2004-2008 TMate Software Ltd.  All rights reserved.
   4  *
   5  * This software is licensed as described in the file COPYING, which
   6  * you should have received as part of this distribution.  The terms
   7  * are also available at http://svnkit.com/license.html.
   8  * If newer versions of this license are posted there, you may use a
   9  * newer version instead, at your option.
  10  * ====================================================================
  11  */
  12 package org.tmatesoft.svn.examples.wc;
  13 
  14 import java.io.File;
  15 import java.io.IOException;
  16 import java.util.Collections;
  17 
  18 import org.tmatesoft.svn.core.SVNCommitInfo;
  19 import org.tmatesoft.svn.core.SVNDepth;
  20 import org.tmatesoft.svn.core.SVNException;
  21 import org.tmatesoft.svn.core.SVNPropertyValue;
  22 import org.tmatesoft.svn.core.SVNURL;
  23 import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
  24 import org.tmatesoft.svn.core.wc.ISVNConflictHandler;
  25 import org.tmatesoft.svn.core.wc.SVNClientManager;
  26 import org.tmatesoft.svn.core.wc.SVNCommitClient;
  27 import org.tmatesoft.svn.core.wc.SVNConflictChoice;
  28 import org.tmatesoft.svn.core.wc.SVNConflictDescription;
  29 import org.tmatesoft.svn.core.wc.SVNConflictReason;
  30 import org.tmatesoft.svn.core.wc.SVNConflictResult;
  31 import org.tmatesoft.svn.core.wc.SVNCopyClient;
  32 import org.tmatesoft.svn.core.wc.SVNCopySource;
  33 import org.tmatesoft.svn.core.wc.SVNDiffClient;
  34 import org.tmatesoft.svn.core.wc.SVNMergeFileSet;
  35 import org.tmatesoft.svn.core.wc.SVNRevision;
  36 import org.tmatesoft.svn.core.wc.SVNRevisionRange;
  37 import org.tmatesoft.svn.core.wc.SVNWCClient;
  38 
  39 
  40 /**
  41  * @version 1.2.0
  42  * @author  TMate Software Ltd.
  43  */
  44 public class ConflictedMerge {
  45     /**
  46      * Pass the absolute path of the base directory where all example data will be created in 
  47      * arg[0]. The sample will create:
  48      *  
  49      *  - arg[0]/exampleRepository - repository with some test data
  50      *  - arg[0]/exampleWC         - working copy checked out from exampleRepository
  51      */
  52     public static void main (String[] args) {
  53         //initialize SVNKit to work through file:/// protocol
  54         SamplesUtility.initializeFSFSprotocol();
  55         
  56         File baseDirectory = new File(args[0]);
  57         File reposRoot = new File(baseDirectory, "exampleRepository");
  58         File wcRoot = new File(baseDirectory, "exampleWC");
  59         
  60         try {
  61             //first create a repository and fill it with data
  62             SamplesUtility.createRepository(reposRoot);
  63             SVNCommitInfo info = SamplesUtility.createRepositoryTree(reposRoot);
  64             //print out new revision info
  65             System.out.println(info);
  66 
  67             SVNClientManager clientManager = SVNClientManager.newInstance();
  68             
  69             SVNURL reposURL = SVNURL.fromFile(reposRoot);
  70 
  71             //copy A to A_copy in repository (url-to-url copy)
  72             SVNCopyClient copyClient = clientManager.getCopyClient();
  73             SVNURL A_URL = reposURL.appendPath("A", true);
  74             SVNURL copyTargetURL = reposURL.appendPath("A_copy", true);
  75             SVNCopySource copySource = new SVNCopySource(SVNRevision.UNDEFINED, SVNRevision.HEAD, A_URL); 
  76             info = copyClient.doCopy(new SVNCopySource[] { copySource }, copyTargetURL, false, false, true, 
  77                     "copy A to A_copy", null);
  78             //print out new revision info
  79             System.out.println(info);
  80             
  81             //checkout the entire repository tree
  82             SamplesUtility.checkOutWorkingCopy(reposURL, wcRoot);
  83 
  84             
  85             //now make some changes to the A tree
  86             SamplesUtility.writeToFile(new File(wcRoot, "A/B/lambda"), "New text appended to 'lambda'", true);
  87             SamplesUtility.writeToFile(new File(wcRoot, "A/mu"), "New text in 'mu'", false);
  88             
  89             SVNWCClient wcClient = SVNClientManager.newInstance().getWCClient();
  90             wcClient.doSetProperty(new File(wcRoot, "A/B"), "spam", SVNPropertyValue.create("egg"), false, 
  91                     SVNDepth.EMPTY, null, null);
  92 
  93             //commit local changes
  94             SVNCommitClient commitClient = clientManager.getCommitClient();
  95             commitClient.doCommit(new File[] { wcRoot }, false, "committing changes", null, null, false, false, SVNDepth.INFINITY);
  96 
  97             //now make some local changes to the A_copy tree 
  98             //change file contents of A_copy/B/lambda and A_copy/mu
  99             SamplesUtility.writeToFile(new File(wcRoot, "A_copy/B/lambda"), "New text in copied 'lambda'", true);
 100             SamplesUtility.writeToFile(new File(wcRoot, "A_copy/mu"), "New text in copied 'mu'", false);
 101 
 102             //now diff the base revision of the working copy against the repository
 103             SVNDiffClient diffClient = clientManager.getDiffClient();
 104 
 105             /*
 106              * Since we provided no custom ISVNOptions implementation to SVNClientManager, our 
 107              * manager uses DefaultSVNOptions, which is set to all SVN*Client classes which the 
 108              * manager produces. So, we can cast ISVNOptions to DefaultSVNOptions.
 109              */
 110             DefaultSVNOptions options = (DefaultSVNOptions) diffClient.getOptions();
 111             //This way we set a conflict handler which will automatically resolve conflicts for those 
 112             //cases that we would like
 113             options.setConflictHandler(new ConflictResolverHandler());
 114             
 115             /* do the same merge call, merge-tracking feature will merge only those revisions
 116              * which were not still merged.
 117              */ 
 118             SVNRevisionRange rangeToMerge = new SVNRevisionRange(SVNRevision.create(1), SVNRevision.HEAD);
 119             diffClient.doMerge(A_URL, SVNRevision.HEAD, Collections.singleton(rangeToMerge), 
 120                     new File(wcRoot, "A_copy"), SVNDepth.UNKNOWN, true, false, false, false);
 121             
 122         } catch (SVNException svne) {
 123             System.out.println(svne.getErrorMessage());
 124             System.exit(1);
 125         } catch (IOException ioe) {
 126             ioe.printStackTrace();
 127             System.exit(1);
 128         }
 129     }
 130 
 131     /**
 132      * Conflict resolver which always selects the local version of a file.
 133      * 
 134      * @version 1.2.0
 135      * @author  TMate Software Ltd.
 136      */
 137     private static class ConflictResolverHandler implements ISVNConflictHandler {
 138     
 139         public SVNConflictResult handleConflict(SVNConflictDescription conflictDescription) throws SVNException {
 140             SVNConflictReason reason = conflictDescription.getConflictReason();
 141             SVNMergeFileSet mergeFiles = conflictDescription.getMergeFiles();
 142             
 143             SVNConflictChoice choice = SVNConflictChoice.THEIRS_FULL;
 144             if (reason == SVNConflictReason.EDITED) {
 145                 //If the reason why conflict occurred is local edits, chose local version of the file
 146                 //Otherwise the repository version of the file will be chosen.
 147                 choice = SVNConflictChoice.MINE_FULL;
 148             }
 149             System.out.println("Automatically resolving conflict for " + mergeFiles.getWCFile() + 
 150                     ", choosing " + (choice == SVNConflictChoice.MINE_FULL ? "local file" : "repository file"));
 151             return new SVNConflictResult(choice, mergeFiles.getResultFile()); 
 152         }   
 153     
 154     }
 155 }

Remarks on the example

When no custom run-time options object (ISVNOptions) is set to the SVNDiffClient (in our case by SVNClientManager), DefaultSVNOptions will be used. This default implementation of run-time options provides a default implementation of ISVNMerger - DefaultSVNMerger - merge driver which is used to merge files and properties. This DefaultSVNMerger can use the caller's implementation of ISVNConflictHandler for automatic conflict resolution. A conflict handler is provided through DefaultSVNMerger, so we set our custom conflict handler to the DefaultSVNOptions used by our SVNDiffClient.

When you run the program you will see an output similar to this one:

r1 by 'alex' at Thu Sep 04 18:05:00 CEST 2008
r2 by 'alex' at Thu Sep 04 18:05:00 CEST 2008
Automatically resolving conflict for /home/alex/workspace/tmp/exampleWC/A_copy/B/lambda, choosing local file
Automatically resolving conflict for /home/alex/workspace/tmp/exampleWC/A_copy/mu, choosing local file