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