001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.examples.repository;
013:
014: import java.io.File;
015: import java.io.IOException;
016: import java.io.OutputStream;
017:
018: import org.tmatesoft.svn.core.SVNCommitInfo;
019: import org.tmatesoft.svn.core.SVNErrorCode;
020: import org.tmatesoft.svn.core.SVNErrorMessage;
021: import org.tmatesoft.svn.core.SVNException;
022: import org.tmatesoft.svn.core.SVNNodeKind;
023: import org.tmatesoft.svn.core.SVNURL;
024: import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
025: import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
026: import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
027: import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
028: import org.tmatesoft.svn.core.io.ISVNEditor;
029: import org.tmatesoft.svn.core.io.ISVNReporter;
030: import org.tmatesoft.svn.core.io.ISVNReporterBaton;
031: import org.tmatesoft.svn.core.io.SVNRepository;
032: import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
033: import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor;
034: import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
035: import org.tmatesoft.svn.core.wc.SVNWCUtil;
036:
037: /*
038: * This example program export contents of the repository directory into file system using
039: * SVNKit library low level API.
040: *
041: * In general, approach we are using in this example is the same that is used for operations
042: * like 'update', 'remote status', 'diff' or 'checkout'. The export operation is the most
043: * simple one and allows to demonstrate this approach without going too much into the details.
044: *
045: * You may find and an article describing this (update) technique at
046: * http://svnkit.com/kb/dev-guide-update-operation.html
047: *
048: * To perform any update-like operation one have to do the following:
049: *
050: * 1. Report the state of the client's working copy to the Subversion server. Of course, it could be
051: * 'virtual' working copy, not necessary stored in the Subversion wc format or, in case of export or
052: * diff operation there could be no working copy at all, which is reflected in report.
053: *
054: * Report is performed with the help ISVNReporter instance that is passed to the client's ISVNReporterBaton
055: * object at the moment report have to be sent.
056: *
057: * 2. Process instructions received from the server. These instructions describes how to modify working copy
058: * to make it be at the desirable revision. Amount of instructions depends on the report sent by the client.
059: * Different operations process received instructions in different manner. For instance, update operation
060: * updates working copy in the filsystem, remote status operation merely logs files and directories that
061: * have to be updated and displays this information.
062: *
063: * With SVNKit API you may implement your own processing code, e.g. repository replication or custom merging code.
064: * ISVNEditor is the interface which implementations process update instructions sent by the server and in
065: * this example ISVNEditor implementation (ExportEditor) creates files and directories corresponding to those
066: * in the repository.
067: *
068: */
069: public class Export {
070:
071: public static void main(String[] args) {
072: /*
073: * Initialize the library. It must be done before calling any
074: * method of the library.
075: */
076: setupLibrary();
077:
078: /*
079: * Run export example and process error if any.
080: */
081: try {
082: exportExample();
083: } catch (SVNException e) {
084: SVNErrorMessage err = e.getErrorMessage();
085: /*
086: * Display all tree of error messages.
087: * Utility method SVNErrorMessage.getFullMessage() may be used instead of the loop.
088: */
089: while (err != null) {
090: System.err.println(err.getErrorCode().getCode() + " : "
091: + err.getMessage());
092: err = err.getChildErrorMessage();
093: }
094: System.exit(1);
095: }
096: System.exit(0);
097: }
098:
099: private static void exportExample() throws SVNException {
100:
101: SVNURL url = SVNURL
102: .parseURIEncoded("http://svn.svnkit.com/repos/svnkit/trunk/doc");
103: String userName = "foo";
104: String userPassword = "bar";
105:
106: /*
107: * Prepare filesystem directory (export destination).
108: */
109: File exportDir = new File("export");
110: if (exportDir.exists()) {
111: SVNErrorMessage err = SVNErrorMessage.create(
112: SVNErrorCode.IO_ERROR,
113: "Path ''{0}'' already exists", exportDir);
114: throw new SVNException(err);
115: }
116: exportDir.mkdirs();
117:
118: /*
119: * Create an instance of SVNRepository class. This class is the main entry point
120: * for all "low-level" Subversion operations supported by Subversion protocol.
121: *
122: * These operations includes browsing, update and commit operations. See
123: * SVNRepository methods javadoc for more details.
124: */
125: SVNRepository repository = SVNRepositoryFactory.create(url);
126:
127: /*
128: * User's authentication information (name/password) is provided via an
129: * ISVNAuthenticationManager instance. SVNWCUtil creates a default
130: * authentication manager given user's name and password.
131: *
132: * Default authentication manager first attempts to use provided user name
133: * and password and then falls back to the credentials stored in the
134: * default Subversion credentials storage that is located in Subversion
135: * configuration area. If you'd like to use provided user name and password
136: * only you may use BasicAuthenticationManager class instead of default
137: * authentication manager:
138: *
139: * authManager = new BasicAuthenticationsManager(userName, userPassword);
140: *
141: * You may also skip this point - anonymous access will be used.
142: */
143: ISVNAuthenticationManager authManager = SVNWCUtil
144: .createDefaultAuthenticationManager(userName,
145: userPassword);
146: repository.setAuthenticationManager(authManager);
147:
148: /*
149: * Get type of the node located at URL we used to create SVNRepository.
150: *
151: * "" (empty string) is path relative to that URL,
152: * -1 is value that may be used to specify HEAD (latest) revision.
153: */
154: SVNNodeKind nodeKind = repository.checkPath("", -1);
155: if (nodeKind == SVNNodeKind.NONE) {
156: SVNErrorMessage err = SVNErrorMessage.create(
157: SVNErrorCode.UNKNOWN, "No entry at URL ''{0}''",
158: url);
159: throw new SVNException(err);
160: } else if (nodeKind == SVNNodeKind.FILE) {
161: SVNErrorMessage err = SVNErrorMessage
162: .create(
163: SVNErrorCode.UNKNOWN,
164: "Entry at URL ''{0}'' is a file while directory was expected",
165: url);
166: throw new SVNException(err);
167: }
168:
169: /*
170: * Get latest repository revision. We will export repository contents at this very revision.
171: */
172: long latestRevision = repository.getLatestRevision();
173:
174: /*
175: * Create reporterBaton. This class is responsible for reporting 'wc state' to the server.
176: *
177: * In this example it will always report that working copy is empty to receive update
178: * instructions that are sufficient to create complete directories hierarchy and get full
179: * files contents.
180: */
181: ISVNReporterBaton reporterBaton = new ExportReporterBaton(
182: latestRevision);
183:
184: /*
185: * Create editor. This class will process update instructions received from the server and
186: * will create directories and files accordingly.
187: *
188: * As we've reported 'emtpy working copy', server will only send 'addDir/addFile' instructions
189: * and will never ask our editor implementation to modify a file or directory properties.
190: */
191: ISVNEditor exportEditor = new ExportEditor(exportDir);
192:
193: /*
194: * Now ask SVNKit to perform generic 'update' operation using our reporter and editor.
195: *
196: * We are passing:
197: *
198: * - revision from which we would like to export
199: * - null as "target" name, to perform export from the URL SVNRepository was created for,
200: * not from some child directory.
201: * - reporterBaton
202: * - exportEditor.
203: */
204: repository.update(latestRevision, null, true, reporterBaton,
205: exportEditor);
206:
207: System.out.println("Exported revision: " + latestRevision);
208: }
209:
210: /*
211: * ReporterBaton implementation that always reports 'empty wc' state.
212: */
213: private static class ExportReporterBaton implements
214: ISVNReporterBaton {
215:
216: private long exportRevision;
217:
218: public ExportReporterBaton(long revision) {
219: exportRevision = revision;
220: }
221:
222: public void report(ISVNReporter reporter) throws SVNException {
223: try {
224: /*
225: * Here empty working copy is reported.
226: *
227: * ISVNReporter includes methods that allows to report mixed-rev working copy
228: * and even let server know that some files or directories are locally missing or
229: * locked.
230: */
231: reporter.setPath("", null, exportRevision, true);
232:
233: /*
234: * Don't forget to finish the report!
235: */
236: reporter.finishReport();
237: } catch (SVNException svne) {
238: reporter.abortReport();
239: System.out.println("Report failed.");
240: }
241: }
242: }
243:
244: /*
245: * ISVNEditor implementation that will add directories and files into the target directory
246: * accordingly to update instructions sent by the server.
247: */
248: private static class ExportEditor implements ISVNEditor {
249:
250: private File myRootDirectory;
251: private SVNDeltaProcessor myDeltaProcessor;
252:
253: /*
254: * root - the local directory where the node tree is to be exported into.
255: */
256: public ExportEditor(File root) {
257: myRootDirectory = root;
258: /*
259: * Utility class that will help us to transform 'deltas' sent by the
260: * server to the new file contents.
261: */
262: myDeltaProcessor = new SVNDeltaProcessor();
263: }
264:
265: /*
266: * Server reports revision to which application of the further
267: * instructions will update working copy to.
268: */
269: public void targetRevision(long revision) throws SVNException {
270: }
271:
272: /*
273: * Called before sending other instructions.
274: */
275: public void openRoot(long revision) throws SVNException {
276: }
277:
278: /*
279: * Called when a new directory has to be added.
280: *
281: * For each 'addDir' call server will call 'closeDir' method after
282: * all children of the added directory are added.
283: *
284: * This implementation creates corresponding directory below root directory.
285: */
286: public void addDir(String path, String copyFromPath,
287: long copyFromRevision) throws SVNException {
288: File newDir = new File(myRootDirectory, path);
289: if (!newDir.exists()) {
290: if (!newDir.mkdirs()) {
291: SVNErrorMessage err = SVNErrorMessage
292: .create(
293: SVNErrorCode.IO_ERROR,
294: "error: failed to add the directory ''{0}''.",
295: newDir);
296: throw new SVNException(err);
297: }
298: }
299: System.out.println("dir added: " + path);
300: }
301:
302: /*
303: * Called when there is an existing directory that has to be 'opened' either
304: * to modify this directory properties or to process other files and directories
305: * inside this directory.
306: *
307: * In case of export this method will never be called because we reported
308: * that our 'working copy' is empty and so server knows that there are
309: * no 'existing' directories.
310: */
311: public void openDir(String path, long revision)
312: throws SVNException {
313: }
314:
315: /*
316: * Instructs to change opened or added directory property.
317: *
318: * This method is called to update properties set by the user as well
319: * as those created automatically, like "svn:committed-rev".
320: * See SVNProperty class for default property names.
321: *
322: * When property has to be deleted value will be 'null'.
323: */
324: public void changeDirProperty(String name, String value)
325: throws SVNException {
326: }
327:
328: /*
329: * Called when a new file has to be created.
330: *
331: * For each 'addFile' call server will call 'closeFile' method after
332: * sending file properties and contents.
333: *
334: * This implementation creates empty file below root directory, file contents
335: * will be updated later, and for empty files may not be sent at all.
336: */
337: public void addFile(String path, String copyFromPath,
338: long copyFromRevision) throws SVNException {
339: File file = new File(myRootDirectory, path);
340: if (file.exists()) {
341: SVNErrorMessage err = SVNErrorMessage.create(
342: SVNErrorCode.IO_ERROR,
343: "error: exported file ''{0}'' already exists!",
344: file);
345: throw new SVNException(err);
346: }
347: try {
348: file.createNewFile();
349: } catch (IOException e) {
350: SVNErrorMessage err = SVNErrorMessage.create(
351: SVNErrorCode.IO_ERROR,
352: "error: cannot create new file ''{0}''", file);
353: throw new SVNException(err);
354: }
355: }
356:
357: /*
358: * Called when there is an existing files that has to be 'opened' either
359: * to modify file contents or properties.
360: *
361: * In case of export this method will never be called because we reported
362: * that our 'working copy' is empty and so server knows that there are
363: * no 'existing' files.
364: */
365: public void openFile(String path, long revision)
366: throws SVNException {
367: }
368:
369: /*
370: * Instructs to add, modify or delete file property.
371: * In this example we skip this instruction, but 'real' export operation
372: * may inspect 'svn:eol-style' or 'svn:mime-type' property values to
373: * transfor file contents propertly after receiving.
374: */
375: public void changeFileProperty(String path, String name,
376: String value) throws SVNException {
377: }
378:
379: /*
380: * Called before sending 'delta' for a file. Delta may include instructions
381: * on how to create a file or how to modify existing file. In this example
382: * delta will always contain instructions on how to create a new file and so
383: * we set up deltaProcessor with 'null' base file and target file to which we would
384: * like to store the result of delta application.
385: */
386: public void applyTextDelta(String path, String baseChecksum)
387: throws SVNException {
388: myDeltaProcessor.applyTextDelta(null, new File(
389: myRootDirectory, path), false);
390: }
391:
392: /*
393: * Server sends deltas in form of 'diff windows'. Depending on the file size
394: * there may be several diff windows. Utility class SVNDeltaProcessor processes
395: * these windows for us.
396: */
397: public OutputStream textDeltaChunk(String path,
398: SVNDiffWindow diffWindow) throws SVNException {
399: return myDeltaProcessor.textDeltaChunk(diffWindow);
400: }
401:
402: /*
403: * Called when all diff windows (delta) is transferred.
404: */
405: public void textDeltaEnd(String path) throws SVNException {
406: myDeltaProcessor.textDeltaEnd();
407: }
408:
409: /*
410: * Called when file update is completed.
411: * This call always matches addFile or openFile call.
412: */
413: public void closeFile(String path, String textChecksum)
414: throws SVNException {
415: System.out.println("file added: " + path);
416: }
417:
418: /*
419: * Called when all child files and directories are processed.
420: * This call always matches addDir, openDir or openRoot call.
421: */
422: public void closeDir() throws SVNException {
423: }
424:
425: /*
426: * Insturcts to delete an entry in the 'working copy'. Of course will not be
427: * called during export operation.
428: */
429: public void deleteEntry(String path, long revision)
430: throws SVNException {
431: }
432:
433: /*
434: * Called when directory at 'path' should be somehow processed,
435: * but authenticated user (or anonymous user) doesn't have enough
436: * access rights to get information on this directory (properties, children).
437: */
438: public void absentDir(String path) throws SVNException {
439: }
440:
441: /*
442: * Called when file at 'path' should be somehow processed,
443: * but authenticated user (or anonymous user) doesn't have enough
444: * access rights to get information on this file (contents, properties).
445: */
446: public void absentFile(String path) throws SVNException {
447: }
448:
449: /*
450: * Called when update is completed.
451: */
452: public SVNCommitInfo closeEdit() throws SVNException {
453: return null;
454: }
455:
456: /*
457: * Called when update is completed with an error or server
458: * requests client to abort update operation.
459: */
460: public void abortEdit() throws SVNException {
461: }
462: }
463:
464: /*
465: * Initializes the library to work with a repository via
466: * different protocols.
467: */
468: private static void setupLibrary() {
469: /*
470: * For using over http:// and https://
471: */
472: DAVRepositoryFactory.setup();
473: /*
474: * For using over svn:// and svn+xxx://
475: */
476: SVNRepositoryFactoryImpl.setup();
477:
478: /*
479: * For using over file:///
480: */
481: FSRepositoryFactory.setup();
482: }
483: }
|