1 package org.appfuse.tool;
2
3 import org.tmatesoft.svn.core.SVNCommitInfo;
4 import org.tmatesoft.svn.core.SVNErrorCode;
5 import org.tmatesoft.svn.core.SVNErrorMessage;
6 import org.tmatesoft.svn.core.SVNException;
7 import org.tmatesoft.svn.core.SVNNodeKind;
8 import org.tmatesoft.svn.core.SVNURL;
9 import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
10 import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
11 import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
12 import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
13 import org.tmatesoft.svn.core.io.ISVNEditor;
14 import org.tmatesoft.svn.core.io.ISVNReporter;
15 import org.tmatesoft.svn.core.io.ISVNReporterBaton;
16 import org.tmatesoft.svn.core.io.SVNRepository;
17 import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
18 import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor;
19 import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
20 import org.tmatesoft.svn.core.wc.SVNWCUtil;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.OutputStream;
25
26 /*
27 * This example program export contents of the repository directory into file system using
28 * SVNKit library low level API.
29 *
30 * In general, approach we are using in this example is the same that is used for operations
31 * like 'update', 'remote status', 'diff' or 'checkout'. The export operation is the most
32 * simple one and allows to demonstrate this approach without going too much into the details.
33 *
34 * You may find and an article describing this (update) technique at
35 * http://svnkit.com/kb/dev-guide-update-operation.html
36 *
37 * To perform any update-like operation one have to do the following:
38 *
39 * 1. Report the state of the client's working copy to the Subversion server. Of course, it could be
40 * 'virtual' working copy, not necessary stored in the Subversion wc format or, in case of export or
41 * diff operation there could be no working copy at all, which is reflected in report.
42 *
43 * Report is performed with the help ISVNReporter instance that is passed to the client's ISVNReporterBaton
44 * object at the moment report have to be sent.
45 *
46 * 2. Process instructions received from the server. These instructions describes how to modify working copy
47 * to make it be at the desirable revision. Amount of instructions depends on the report sent by the client.
48 * Different operations process received instructions in different manner. For instance, update operation
49 * updates working copy in the filsystem, remote status operation merely logs files and directories that
50 * have to be updated and displays this information.
51 *
52 * With SVNKit API you may implement your own processing code, e.g. repository replication or custom merging code.
53 * ISVNEditor is the interface which implementations process update instructions sent by the server and in
54 * this example ISVNEditor implementation (ExportEditor) creates files and directories corresponding to those
55 * in the repository.
56 *
57 */
58 public class SubversionUtils {
59 private String url;
60 private String destinationDirectory;
61
62 public SubversionUtils(String url, String destinationDirectory) {
63 /*
64 * Initialize the library. It must be done before calling any
65 * method of the library.
66 */
67 setupLibrary();
68 this.url = url;
69 this.destinationDirectory = destinationDirectory;
70 }
71
72 public void export() throws SVNException {
73 SVNURL url = SVNURL.parseURIEncoded(this.url);
74 String userName = "guest";
75 String userPassword = "guest";
76
77 /*
78 * Prepare filesystem directory (export destination).
79 */
80 File exportDir = new File(this.destinationDirectory);
81 /*if (exportDir.exists()) {
82 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Path ''{0}'' already exists", exportDir);
83 throw new SVNException(err);
84 }
85 exportDir.mkdirs();*/
86
87 /*
88 * Create an instance of SVNRepository class. This class is the main entry point
89 * for all "low-level" Subversion operations supported by Subversion protocol.
90 *
91 * These operations includes browsing, update and commit operations. See
92 * SVNRepository methods javadoc for more details.
93 */
94 SVNRepository repository = SVNRepositoryFactory.create(url);
95
96 /*
97 * User's authentication information (name/password) is provided via an
98 * ISVNAuthenticationManager instance. SVNWCUtil creates a default
99 * authentication manager given user's name and password.
100 *
101 * Default authentication manager first attempts to use provided user name
102 * and password and then falls back to the credentials stored in the
103 * default Subversion credentials storage that is located in Subversion
104 * configuration area. If you'd like to use provided user name and password
105 * only you may use BasicAuthenticationManager class instead of default
106 * authentication manager:
107 *
108 * authManager = new BasicAuthenticationsManager(userName, userPassword);
109 *
110 * You may also skip this point - anonymous access will be used.
111 */
112 ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(userName, userPassword);
113 repository.setAuthenticationManager(authManager);
114
115 /*
116 * Get type of the node located at URL we used to create SVNRepository.
117 *
118 * "" (empty string) is path relative to that URL,
119 * -1 is value that may be used to specify HEAD (latest) revision.
120 */
121 SVNNodeKind nodeKind = repository.checkPath("", -1);
122 if (nodeKind == SVNNodeKind.NONE) {
123 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "No entry at URL ''{0}''", url);
124 throw new SVNException(err);
125 } else if (nodeKind == SVNNodeKind.FILE) {
126 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Entry at URL ''{0}'' is a file while directory was expected", url);
127 throw new SVNException(err);
128 }
129
130 /*
131 * Get latest repository revision. We will export repository contents at this very revision.
132 */
133 long latestRevision = repository.getLatestRevision();
134
135 /*
136 * Create reporterBaton. This class is responsible for reporting 'wc state' to the server.
137 *
138 * In this example it will always report that working copy is empty to receive update
139 * instructions that are sufficient to create complete directories hierarchy and get full
140 * files contents.
141 */
142 ISVNReporterBaton reporterBaton = new ExportReporterBaton(latestRevision);
143
144 /*
145 * Create editor. This class will process update instructions received from the server and
146 * will create directories and files accordingly.
147 *
148 * As we've reported 'emtpy working copy', server will only send 'addDir/addFile' instructions
149 * and will never ask our editor implementation to modify a file or directory properties.
150 */
151 ISVNEditor exportEditor = new ExportEditor(exportDir);
152
153 /*
154 * Now ask SVNKit to perform generic 'update' operation using our reporter and editor.
155 *
156 * We are passing:
157 *
158 * - revision from which we would like to export
159 * - null as "target" name, to perform export from the URL SVNRepository was created for,
160 * not from some child directory.
161 * - reporterBaton
162 * - exportEditor.
163 */
164 repository.update(latestRevision, null, true, reporterBaton, exportEditor);
165
166 //System.out.println("Exported revision: " + latestRevision);
167 }
168
169 /*
170 * ReporterBaton implementation that always reports 'empty wc' state.
171 */
172 private static class ExportReporterBaton implements ISVNReporterBaton {
173
174 private long exportRevision;
175
176 public ExportReporterBaton(long revision){
177 exportRevision = revision;
178 }
179
180 public void report(ISVNReporter reporter) throws SVNException {
181 try {
182 /*
183 * Here empty working copy is reported.
184 *
185 * ISVNReporter includes methods that allows to report mixed-rev working copy
186 * and even let server know that some files or directories are locally missing or
187 * locked.
188 */
189 reporter.setPath("", null, exportRevision, true);
190
191 /*
192 * Don't forget to finish the report!
193 */
194 reporter.finishReport();
195 } catch (SVNException svne) {
196 reporter.abortReport();
197 System.out.println("Report failed.");
198 }
199 }
200 }
201
202 /*
203 * ISVNEditor implementation that will add directories and files into the target directory
204 * accordingly to update instructions sent by the server.
205 */
206 private static class ExportEditor implements ISVNEditor {
207
208 private File myRootDirectory;
209 private SVNDeltaProcessor myDeltaProcessor;
210
211 /*
212 * root - the local directory where the node tree is to be exported into.
213 */
214 public ExportEditor(File root) {
215 myRootDirectory = root;
216 /*
217 * Utility class that will help us to transform 'deltas' sent by the
218 * server to the new file contents.
219 */
220 myDeltaProcessor = new SVNDeltaProcessor();
221 }
222
223 /*
224 * Server reports revision to which application of the further
225 * instructions will update working copy to.
226 */
227 public void targetRevision(long revision) throws SVNException {
228 }
229
230 /*
231 * Called before sending other instructions.
232 */
233 public void openRoot(long revision) throws SVNException {
234 }
235
236 /*
237 * Called when a new directory has to be added.
238 *
239 * For each 'addDir' call server will call 'closeDir' method after
240 * all children of the added directory are added.
241 *
242 * This implementation creates corresponding directory below root directory.
243 */
244 public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException {
245 File newDir = new File(myRootDirectory, path);
246 if (!newDir.exists()) {
247 if (!newDir.mkdirs()) {
248 //SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: failed to add the directory ''{0}''.", newDir);
249 //throw new SVNException(err);
250 //System.err.println(err.getMessage() + "Ignoring and not overriding.");
251 }
252 }
253 //System.out.println("dir added: " + path);
254 }
255
256 /*
257 * Called when there is an existing directory that has to be 'opened' either
258 * to modify this directory properties or to process other files and directories
259 * inside this directory.
260 *
261 * In case of export this method will never be called because we reported
262 * that our 'working copy' is empty and so server knows that there are
263 * no 'existing' directories.
264 */
265 public void openDir(String path, long revision) throws SVNException {
266 }
267
268 /*
269 * Instructs to change opened or added directory property.
270 *
271 * This method is called to update properties set by the user as well
272 * as those created automatically, like "svn:committed-rev".
273 * See SVNProperty class for default property names.
274 *
275 * When property has to be deleted value will be 'null'.
276 */
277 public void changeDirProperty(String name, String value) throws SVNException {
278 }
279
280 /*
281 * Called when a new file has to be created.
282 *
283 * For each 'addFile' call server will call 'closeFile' method after
284 * sending file properties and contents.
285 *
286 * This implementation creates empty file below root directory, file contents
287 * will be updated later, and for empty files may not be sent at all.
288 */
289 public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException {
290 File file = new File(myRootDirectory, path);
291 if (file.exists()) {
292 //SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: exported file ''{0}'' already exists!", file);
293 //System.err.println(err.getMessage() + "Ignoring and not overriding.");
294 //throw new SVNException(err);
295 } else {
296 try {
297 file.createNewFile();
298 } catch (IOException e) {
299 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: cannot create new file ''{0}''", file);
300 throw new SVNException(err);
301 }
302 }
303 }
304
305 /*
306 * Called when there is an existing files that has to be 'opened' either
307 * to modify file contents or properties.
308 *
309 * In case of export this method will never be called because we reported
310 * that our 'working copy' is empty and so server knows that there are
311 * no 'existing' files.
312 */
313 public void openFile(String path, long revision) throws SVNException {
314 }
315
316 /*
317 * Instructs to add, modify or delete file property.
318 * In this example we skip this instruction, but 'real' export operation
319 * may inspect 'svn:eol-style' or 'svn:mime-type' property values to
320 * transfor file contents propertly after receiving.
321 */
322 public void changeFileProperty(String path, String name, String value) throws SVNException {
323 }
324
325 /*
326 * Called before sending 'delta' for a file. Delta may include instructions
327 * on how to create a file or how to modify existing file. In this example
328 * delta will always contain instructions on how to create a new file and so
329 * we set up deltaProcessor with 'null' base file and target file to which we would
330 * like to store the result of delta application.
331 */
332 public void applyTextDelta(String path, String baseChecksum) throws SVNException {
333 myDeltaProcessor.applyTextDelta(null, new File(myRootDirectory, path), false);
334 }
335
336 /*
337 * Server sends deltas in form of 'diff windows'. Depending on the file size
338 * there may be several diff windows. Utility class SVNDeltaProcessor processes
339 * these windows for us.
340 */
341 public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException {
342 return myDeltaProcessor.textDeltaChunk(diffWindow);
343 }
344
345 /*
346 * Called when all diff windows (delta) is transferred.
347 */
348 public void textDeltaEnd(String path) throws SVNException {
349 myDeltaProcessor.textDeltaEnd();
350 }
351
352 /*
353 * Called when file update is completed.
354 * This call always matches addFile or openFile call.
355 */
356 public void closeFile(String path, String textChecksum) throws SVNException {
357 //System.out.println("file added: " + path);
358 }
359
360 /*
361 * Called when all child files and directories are processed.
362 * This call always matches addDir, openDir or openRoot call.
363 */
364 public void closeDir() throws SVNException {
365 }
366
367 /*
368 * Insturcts to delete an entry in the 'working copy'. Of course will not be
369 * called during export operation.
370 */
371 public void deleteEntry(String path, long revision) throws SVNException {
372 }
373
374 /*
375 * Called when directory at 'path' should be somehow processed,
376 * but authenticated user (or anonymous user) doesn't have enough
377 * access rights to get information on this directory (properties, children).
378 */
379 public void absentDir(String path) throws SVNException {
380 }
381
382 /*
383 * Called when file at 'path' should be somehow processed,
384 * but authenticated user (or anonymous user) doesn't have enough
385 * access rights to get information on this file (contents, properties).
386 */
387 public void absentFile(String path) throws SVNException {
388 }
389
390 /*
391 * Called when update is completed.
392 */
393 public SVNCommitInfo closeEdit() throws SVNException {
394 return null;
395 }
396
397 /*
398 * Called when update is completed with an error or server
399 * requests client to abort update operation.
400 */
401 public void abortEdit() throws SVNException {
402 }
403 }
404
405 /*
406 * Initializes the library to work with a repository via
407 * different protocols.
408 */
409 private static void setupLibrary() {
410 /*
411 * For using over http:// and https://
412 */
413 DAVRepositoryFactory.setup();
414 /*
415 * For using over svn:// and svn+xxx://
416 */
417 SVNRepositoryFactoryImpl.setup();
418
419 /*
420 * For using over file:///
421 */
422 FSRepositoryFactory.setup();
423 }
424 }