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, 
  96                                   false, SVNDepth.INFINITY);
  97 
  98             //now make some local changes to the A_copy tree 
  99             //change file contents of A_copy/B/lambda and A_copy/mu
 100             SamplesUtility.writeToFile(new File(wcRoot, "A_copy/B/lambda"), "New text in copied 'lambda'", true);
 101             SamplesUtility.writeToFile(new File(wcRoot, "A_copy/mu"), "New text in copied 'mu'", false);
 102 
 103             //now diff the base revision of the working copy against the repository
 104             SVNDiffClient diffClient = clientManager.getDiffClient();
 105 
 106             /*
 107              * Since we provided no custom ISVNOptions implementation to SVNClientManager, our 
 108              * manager uses DefaultSVNOptions, which is set to all SVN*Client classes which the 
 109              * manager produces. So, we can cast ISVNOptions to DefaultSVNOptions.
 110              */
 111             DefaultSVNOptions options = (DefaultSVNOptions) diffClient.getOptions();
 112             //This way we set a conflict handler which will automatically resolve conflicts for those 
 113             //cases that we would like
 114             options.setConflictHandler(new ConflictResolverHandler());
 115             
 116             /* do the same merge call, merge-tracking feature will merge only those revisions
 117              * which were not still merged.
 118              */ 
 119             SVNRevisionRange rangeToMerge = new SVNRevisionRange(SVNRevision.create(1), SVNRevision.HEAD);
 120             diffClient.doMerge(A_URL, SVNRevision.HEAD, Collections.singleton(rangeToMerge), 
 121                     new File(wcRoot, "A_copy"), SVNDepth.UNKNOWN, true, false, false, false);
 122             
 123         } catch (SVNException svne) {
 124             System.out.println(svne.getErrorMessage());
 125             System.exit(1);
 126         } catch (IOException ioe) {
 127             ioe.printStackTrace();
 128             System.exit(1);
 129         }
 130     }
 131 
 132     /**
 133      * Conflict resolver which always selects the local version of a file.
 134      * 
 135      * @version 1.2.0
 136      * @author  TMate Software Ltd.
 137      */
 138     private static class ConflictResolverHandler implements ISVNConflictHandler {
 139     
 140         public SVNConflictResult handleConflict(SVNConflictDescription conflictDescription) throws SVNException {
 141             SVNConflictReason reason = conflictDescription.getConflictReason();
 142             SVNMergeFileSet mergeFiles = conflictDescription.getMergeFiles();
 143             
 144             SVNConflictChoice choice = SVNConflictChoice.THEIRS_FULL;
 145             if (reason == SVNConflictReason.EDITED) {
 146                 //If the reason why conflict occurred is local edits, chose local version of the file
 147                 //Otherwise the repository version of the file will be chosen.
 148                 choice = SVNConflictChoice.MINE_FULL;
 149             }
 150             System.out.println("Automatically resolving conflict for " + mergeFiles.getWCFile() + 
 151                     ", choosing " + (choice == SVNConflictChoice.MINE_FULL ? "local file" : "repository file"));
 152             return new SVNConflictResult(choice, mergeFiles.getResultFile()); 
 153         }   
 154     
 155     }
 156 }

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 DefaultSVNOptions, so we set our custom conflict handler - ConflictResolverHandler - to the DefaultSVNOptions used by our SVNDiffClient. Our handler instructs the merge driver to choose a local working copy file in case of a conflict given that the conflict itself is caused by our local edits to the file. In all other cases we just say:"Take what the repository is giving us".

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

Merging from trunk to a branch with conflicts (last edited 2008-09-04 16:40:51 by 194)