package org.tmatesoft.svn.core.internal.wc16;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.util.*;
import org.tmatesoft.svn.core.internal.wc.*;
import org.tmatesoft.svn.core.internal.wc.admin.*;
import org.tmatesoft.svn.core.io.*;
import org.tmatesoft.svn.core.wc.*;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
import java.io.File;
import java.io.OutputStream;
import java.util.*;
/**
* This class provides methods which allow to check out, update, switch and
* relocate a Working Copy as well as export an unversioned directory or file
* from a repository.
* <p>
* Here's a list of the <b>SVNUpdateClient</b>'s methods matched against
* corresponing commands of the SVN command line client:
* <table cellpadding="3" cellspacing="1" border="0" width="40%" bgcolor="#999933">
* <tr bgcolor="#ADB8D9" align="left">
* <td><b>SVNKit</b></td>
* <td><b>Subversion</b></td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doCheckout()</td>
* <td>'svn checkout'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doUpdate()</td>
* <td>'svn update'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doSwitch()</td>
* <td>'svn switch'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doRelocate()</td>
* <td>'svn switch --relocate oldURL newURL'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doExport()</td>
* <td>'svn export'</td>
* </tr>
* </table>
*
* @version 1.3
* @author TMate Software Ltd.
* @since 1.2
* @see <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a>
*/
public class SVNUpdateClient16 extends SVNBasicDelegate {
private ISVNExternalsHandler myExternalsHandler;
private boolean myIsUpdateLocksOnDemand;
private boolean myIsExportExpandsKeywords;
/**
* Constructs and initializes an <b>SVNUpdateClient</b> object with the
* specified run-time configuration and authentication drivers.
* <p>
* If <code>options</code> is <span class="javakeyword">null</span>, then
* this <b>SVNUpdateClient</b> will be using a default run-time
* configuration driver which takes client-side settings from the default
* SVN's run-time configuration area but is not able to change those
* settings (read more on {@link ISVNOptions} and {@link SVNWCUtil}).
* <p>
* If <code>authManager</code> is <span class="javakeyword">null</span>,
* then this <b>SVNUpdateClient</b> will be using a default authentication
* and network layers driver (see
* {@link SVNWCUtil#createDefaultAuthenticationManager()}) which uses
* server-side settings and auth storage from the default SVN's run-time
* configuration area (or system properties if that area is not found).
*
* @param authManager
* an authentication and network layers driver
* @param options
* a run-time configuration options driver
*/
public SVNUpdateClient16(ISVNAuthenticationManager authManager, ISVNOptions options) {
super(authManager, options);
setExportExpandsKeywords(true);
setExternalsHandler(ISVNExternalsHandler.DEFAULT);
}
/**
* Constructs and initializes an <b>SVNUpdateClient</b> object with the
* specified run-time configuration and authentication drivers.
* <p/>
* If <code>options</code> is <span class="javakeyword">null</span>, then
* this <b>SVNUpdateClient</b> will be using a default run-time
* configuration driver which takes client-side settings from the default
* SVN's run-time configuration area but is not able to change those
* settings (read more on {@link ISVNOptions} and {@link SVNWCUtil}).
* <p/>
* If <code>repositoryPool</code> is <span class="javakeyword">null</span>,
* then {@link org.tmatesoft.svn.core.io.SVNRepositoryFactory} will be used
* to create {@link SVNRepository repository access objects}.
*
* @param repositoryPool
* a repository pool object
* @param options
* a run-time configuration options driver
*/
public SVNUpdateClient16(ISVNRepositoryPool repositoryPool, ISVNOptions options) {
super(repositoryPool, options);
setExportExpandsKeywords(true);
setExternalsHandler(ISVNExternalsHandler.DEFAULT);
}
/**
* Sets an externals handler to be used by this client object.
*
* @param externalsHandler
* user's implementation of {@link ISVNExternalsHandler}
* @see #getExternalsHandler()
* @since 1.2
*/
public void setExternalsHandler(ISVNExternalsHandler externalsHandler) {
if (externalsHandler == null) {
externalsHandler = ISVNExternalsHandler.DEFAULT;
}
this.myExternalsHandler = externalsHandler;
}
/**
* Returns an externals handler used by this update client.
* <p/>
* If no user's handler is provided then
* {@link ISVNExternalsHandler#DEFAULT} is returned and used by this client
* object by default.
* <p/>
* For more information what externals handlers are for, please, refer to
* {@link ISVNExternalsHandler}.
*
* @return externals handler being in use
* @see #setExternalsHandler(ISVNExternalsHandler)
* @since 1.2
*/
public ISVNExternalsHandler getExternalsHandler() {
return this.myExternalsHandler;
}
/**
* Brings the Working Copy item up-to-date with repository changes at the
* specified revision.
* <p>
* As a revision <b>SVNRevision</b>'s pre-defined constant fields can be
* used. For example, to update the Working Copy to the latest revision of
* the repository use {@link SVNRevision#HEAD HEAD}.
*
* @param file
* the Working copy item to be updated
* @param revision
* the desired revision against which the item will be updated
* @param recursive
* if <span class="javakeyword">true</span> and <code>file</code>
* is a directory then the entire tree will be updated, otherwise
* if <span class="javakeyword">false</span> - only items located
* immediately in the directory itself
* @return the revision number to which <code>file</code> was updated to
* @throws SVNException
* @deprecated use
* {@link #doUpdate(File,SVNRevision,SVNDepth,boolean,boolean)}
* instead
*/
public long doUpdate(File file, SVNRevision revision, boolean recursive) throws SVNException {
return doUpdate(file, revision, SVNDepth.fromRecurse(recursive), false, false);
}
/**
* @param file
* @param revision
* @param recursive
* @param force
* @return actual revision number
* @throws SVNException
* @deprecated use
* {@link #doUpdate(File,SVNRevision,SVNDepth,boolean,boolean)}
* instead
*/
public long doUpdate(File file, SVNRevision revision, boolean recursive, boolean force) throws SVNException {
return doUpdate(file, revision, SVNDepth.fromRecurse(recursive), force, false);
}
/**
* Updates working trees <code>paths</code> to <code>revision</code>.
* Unversioned paths that are direct children of a versioned path will cause
* an update that attempts to add that path, other unversioned paths are
* skipped.
* <p/>
* <code>revision</code> must represent a valid revision number (
* {@link SVNRevision#getNumber()} >= 0), or date (
* {@link SVNRevision#getDate()} != <span class="javakeyword">true</span>),
* or be equal to {@link SVNRevision#HEAD}. If <code>revision</code> does
* not meet these requirements, an exception with the error code
* {@link SVNErrorCode#CLIENT_BAD_REVISION} is thrown.
* <p/>
* The paths in <code>paths</code> can be from multiple working copies from
* multiple repositories, but even if they all come from the same repository
* there is no guarantee that revision represented by
* {@link SVNRevision#HEAD}will remain the same as each path is updated.
* <p/>
* If externals are {@link #isIgnoreExternals() ignored}, doesn't process
* externals definitions as part of this operation.
* <p/>
* If <code>depth</code> is {@link SVNDepth#INFINITY}, updates fully
* recursively. Else if it is {@link SVNDepth#IMMEDIATES} or
* {@link SVNDepth#FILES}, updates each target and its file entries, but not
* its subdirectories. Else if {@link SVNDepth#EMPTY}, updates exactly each
* target, nonrecursively (essentially, updates the target's properties).
* <p/>
* If <code>depth</code> is {@link SVNDepth#UNKNOWN}, takes the working
* depth from <code>paths</code> and then behaves as described above.
* <p/>
* If <code>depthIsSticky</code> is set and <code>depth</code> is not
* {@link SVNDepth#UNKNOWN}, then in addition to updating <code>paths</code>
* , also sets their sticky ambient depth value to <code>depth</codes>.
* <p/>
* If <code>allowUnversionedObstructions</code> is <span
* class="javakeyword">true</span> then the update tolerates existing
* unversioned items that obstruct added paths. Only obstructions of the
* same type (file or dir) as the added item are tolerated. The text of
* obstructing files is left as-is, effectively treating it as a user
* modification after the update. Working properties of obstructing items
* are set equal to the base properties. If
* <code>allowUnversionedObstructions</code> is <span
* class="javakeyword">false</span> then the update will abort if there are
* any unversioned obstructing items.
* <p/>
* If the caller's {@link ISVNEventHandler} is non-<span
* class="javakeyword">null</span>, it is invoked for each item handled by
* the update, and also for files restored from text-base. Also
* {@link ISVNEventHandler#checkCancelled()} will be used at various places
* during the update to check whether the caller wants to stop the update.
* <p/>
* Before updating a next path from <code>paths</code> this method calls
* {@link #handlePathListItem(File)} passing the path to it.
* <p/>
* This operation requires repository access (in case the repository is not
* on the same machine, network connection is established).
*
* @param paths
* working copy paths
* @param revision
* revision to update to
* @param depth
* tree depth to update
* @param allowUnversionedObstructions
* flag that allows tolerating unversioned items during update
* @param depthIsSticky
* flag that controls whether the requested depth should be
* written to the working copy
* @return an array of <code>long</code> revisions with each element set to
* the revision to which <code>revision</code> was resolved
* @throws SVNException
* @since 1.2, SVN 1.5
*/
public long[] doUpdate(File[] paths, SVNRevision revision, SVNDepth depth, boolean allowUnversionedObstructions, boolean depthIsSticky) throws SVNException {
if (paths == null) {
return new long[0];
}
Collection revisions = new LinkedList();
for (int i = 0; i < paths.length; i++) {
checkCancelled();
File path = paths[i];
try {
setEventPathPrefix("");
handlePathListItem(path);
long rev = doUpdate(path, revision, depth, allowUnversionedObstructions, depthIsSticky);
revisions.add(new Long(rev));
} catch (SVNException svne) {
if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) {
SVNEvent skipEvent = SVNEventFactory.createSVNEvent(path, SVNNodeKind.UNKNOWN, null, SVNRepository.INVALID_REVISION, SVNEventAction.SKIP, SVNEventAction.UPDATE_COMPLETED, null,
null);
dispatchEvent(skipEvent);
revisions.add(new Long(-1));
continue;
}
throw svne;
} finally {
setEventPathPrefix(null);
}
}
sleepForTimeStamp();
long[] result = new long[revisions.size()];
int i = 0;
for (Iterator revs = revisions.iterator(); revs.hasNext();) {
Long value = (Long) revs.next();
result[i++] = value.longValue();
}
return result;
}
/**
* Updates working copy <code></code> to <code>revision</code>. Unversioned
* paths that are direct children of a versioned path will cause an update
* that attempts to add that path, other unversioned paths are skipped.
* <p/>
* <code>revision</code> must represent a valid revision number (
* {@link SVNRevision#getNumber()} >= 0), or date (
* {@link SVNRevision#getDate()} != <span class="javakeyword">true</span>),
* or be equal to {@link SVNRevision#HEAD}. If <code>revision</code> does
* not meet these requirements, an exception with the error code
* {@link SVNErrorCode#CLIENT_BAD_REVISION} is thrown.
* <p/>
* If externals are {@link #isIgnoreExternals() ignored}, doesn't process
* externals definitions as part of this operation.
* <p/>
* If <code>depth</code> is {@link SVNDepth#INFINITY}, updates fully
* recursively. Else if it is {@link SVNDepth#IMMEDIATES} or
* {@link SVNDepth#FILES}, updates <code>path</code> and its file entries,
* but not its subdirectories. Else if {@link SVNDepth#EMPTY}, updates
* exactly <code>path</code>, nonrecursively (essentially, updates the
* target's properties).
* <p/>
* If <code>depth</code> is {@link SVNDepth#UNKNOWN}, takes the working
* depth from <code>path</code> and then behaves as described above.
* <p/>
* If <code>depthIsSticky</code> is set and <code>depth</code> is not
* {@link SVNDepth#UNKNOWN}, then in addition to updating <code>path</code>,
* also sets its sticky ambient depth value to <code>depth</codes>.
* <p/>
* If <code>allowUnversionedObstructions</code> is <span
* class="javakeyword">true</span> then the update tolerates existing
* unversioned items that obstruct added paths. Only obstructions of the
* same type (file or dir) as the added item are tolerated. The text of
* obstructing files is left as-is, effectively treating it as a user
* modification after the update. Working properties of obstructing items
* are set equal to the base properties. If
* <code>allowUnversionedObstructions</code> is <span
* class="javakeyword">false</span> then the update will abort if there are
* any unversioned obstructing items.
* <p/>
* If the caller's {@link ISVNEventHandler} is non-<span
* class="javakeyword">null</span>, it is invoked for each item handled by
* the update, and also for files restored from text-base. Also
* {@link ISVNEventHandler#checkCancelled()} will be used at various places
* during the update to check whether the caller wants to stop the update.
* <p/>
* This operation requires repository access (in case the repository is not
* on the same machine, network connection is established).
*
* @param path
* working copy path
* @param revision
* revision to update to
* @param depth
* tree depth to update
* @param allowUnversionedObstructions
* flag that allows tollerating unversioned items during update
* @param depthIsSticky
* flag that controls whether the requested depth should be
* written to the working copy
* @return revision to which <code>revision</code> was resolved
* @throws SVNException
* @since 1.2, SVN 1.5
*/
public long doUpdate(File path, SVNRevision revision, SVNDepth depth, boolean allowUnversionedObstructions, boolean depthIsSticky) throws SVNException {
return update(path, revision, depth, allowUnversionedObstructions, depthIsSticky, true);
}
private long doSwitchImpl(SVNWCAccess wcAccess, File path, SVNURL url, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, boolean allowUnversionedObstructions, boolean depthIsSticky)
throws SVNException {
if (!SVNWCUtil.isVersionedDirectory(path.isDirectory() ? path : path.getParentFile())) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND,
"''{0}'' does not appear to be a working copy path", path);
SVNErrorManager.error(err, SVNLogType.CLIENT);
}
if (depth == SVNDepth.UNKNOWN) {
depthIsSticky = false;
}
if (depthIsSticky && depth == SVNDepth.EXCLUDE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Cannot both exclude and switch a path");
SVNErrorManager.error(err, SVNLogType.WC);
}
boolean closeAccess = wcAccess == null;
try {
SVNAdminAreaInfo info = null;
if (wcAccess != null) {
SVNWCAccess tmpAccess = null;
try {
tmpAccess = createWCAccess();
info = tmpAccess.openAnchor(path, false, SVNWCAccess.INFINITE_DEPTH);
} finally {
tmpAccess.close();
}
SVNAdminArea anchor = info.getAnchor();
SVNAdminArea target = info.getTarget();
anchor = wcAccess.retrieve(anchor.getRoot());
target = wcAccess.retrieve(target.getRoot());
info.setAnchor(anchor);
info.setTarget(target);
info.setWCAccess(wcAccess);
} else {
wcAccess = createWCAccess();
info = wcAccess.openAnchor(path, true, SVNWCAccess.INFINITE_DEPTH);
}
final SVNReporter reporter = new SVNReporter(info, path, true, false, depth, false, false, !depthIsSticky, getDebugLog());
SVNAdminArea anchorArea = info.getAnchor();
SVNEntry entry = anchorArea.getVersionedEntry(anchorArea.getThisDirName(), false);
SVNURL sourceURL = entry.getSVNURL();
if (sourceURL == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "Directory ''{0}'' has no URL", anchorArea.getRoot());
SVNErrorManager.error(err, SVNLogType.WC);
}
long[] revs = new long[1];
SVNRepository repository = createRepository(url, null, anchorArea, pegRevision, revision, revs);
long revNumber = revs[0];
url = repository.getLocation();
SVNURL sourceRoot = repository.getRepositoryRoot(true);
if (!SVNPathUtil.isAncestor(sourceRoot.toString(), sourceURL.toString())) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SWITCH, "''{0}''\nis not the same repository as\n''{1}''", new Object[] {
sourceURL.toString(), sourceRoot.toString()
});
SVNErrorManager.error(err, SVNLogType.WC);
}
if (depthIsSticky && depth.compareTo(SVNDepth.INFINITY) < 0) {
SVNEntry targetEntry = anchorArea.getEntry(info.getTargetName(), true);
if (targetEntry != null && targetEntry.isDirectory()) {
SVNWCManager.crop(info, depth);
}
}
repository.setLocation(sourceURL, false);
String[] preservedExts = getOptions().getPreservedConflictFileExtensions();
ISVNUpdateEditor editor = wcAccess.createUpdateEditor(info, url.toString(), allowUnversionedObstructions, depthIsSticky, depth, preservedExts, null, false);
ISVNEditor filterEditor = SVNAmbientDepthFilterEditor.wrap(editor, info, depthIsSticky);
String target = "".equals(info.getTargetName()) ? null : info.getTargetName();
repository.update(url, revNumber, target, depth, reporter, SVNCancellableEditor.newInstance(filterEditor, this, getDebugLog()));
long targetRevision = editor.getTargetRevision();
if (targetRevision >= 0 && !isIgnoreExternals() && depth.isRecursive()) {
url = target == null ? url : url.removePathTail();
handleExternals(wcAccess, info.getAnchor().getRoot(), info.getOldExternals(), info.getNewExternals(), info.getDepths(), url, sourceRoot, depth, false, true);
}
dispatchEvent(SVNEventFactory.createSVNEvent(info.getTarget().getRoot(), SVNNodeKind.NONE, null, targetRevision, SVNEventAction.UPDATE_COMPLETED, null, null, null, reporter
.getReportedFilesCount(), reporter.getTotalFilesCount()));
return targetRevision;
} finally {
if (closeAccess) {
wcAccess.close();
}
sleepForTimeStamp();
}
}
private long update(File path, SVNRevision revision, SVNDepth depth, boolean allowUnversionedObstructions, boolean depthIsSticky, boolean sendCopyFrom) throws SVNException {
depth = depth == null ? SVNDepth.UNKNOWN : depth;
if (depth == SVNDepth.UNKNOWN) {
depthIsSticky = false;
}
path = path.getAbsoluteFile();
SVNWCAccess wcAccess = createWCAccess();
SVNAdminAreaInfo adminInfo = null;
int admOpenDepth = depthIsSticky ? -1 : getLevelsToLockFromDepth(depth);
try {
if (isUpdateLocksOnDemand()) {
wcAccess.openAnchor(path, true, 0);
wcAccess.close();
}
adminInfo = wcAccess.openAnchor(path, !isUpdateLocksOnDemand(), admOpenDepth);
SVNAdminArea anchorArea = adminInfo.getAnchor();
SVNEntry entry = anchorArea.getEntry(anchorArea.getThisDirName(), false);
SVNURL url = entry.getSVNURL();
if (url == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "Entry ''{0}'' has no URL", anchorArea.getRoot());
SVNErrorManager.error(err, SVNLogType.WC);
}
if (depthIsSticky && depth.compareTo(SVNDepth.INFINITY) < 0) {
SVNEntry targetEntry = anchorArea.getEntry(adminInfo.getTargetName(), true);
if (targetEntry != null && targetEntry.isDirectory()) {
SVNWCManager.crop(adminInfo, depth);
if (depth == SVNDepth.EXCLUDE) {
return -1;
}
}
}
String[] preservedExts = getOptions().getPreservedConflictFileExtensions();
SVNRepository repos = createRepository(url, anchorArea.getRoot(), wcAccess, true);
boolean serverSupportsDepth = repos.hasCapability(SVNCapability.DEPTH);
final SVNReporter reporter = new SVNReporter(adminInfo, path, true, !serverSupportsDepth, depth, isUpdateLocksOnDemand(), false, !depthIsSticky, getDebugLog());
String target = "".equals(adminInfo.getTargetName()) ? null : adminInfo.getTargetName();
long revNumber = getRevisionNumber(revision, repos, path);
final SVNURL reposRoot = repos.getRepositoryRoot(true);
wcAccess.setRepositoryRoot(path, reposRoot);
final SVNRepository[] repos2 = new SVNRepository[1];
ISVNFileFetcher fileFetcher = new ISVNFileFetcher() {
public long fetchFile(String path, long revision, OutputStream os, SVNProperties properties) throws SVNException {
SVNURL url = reposRoot.appendPath(SVNPathUtil.removeTail(path), false);
if (repos2[0] == null) {
repos2[0] = createRepository(url, null, null, false);
} else {
repos2[0].setLocation(url, false);
}
return repos2[0].getFile(SVNPathUtil.tail(path), revision, properties, os);
}
};
ISVNUpdateEditor editor = wcAccess.createUpdateEditor(adminInfo, null, allowUnversionedObstructions, depthIsSticky, depth, preservedExts, fileFetcher, isUpdateLocksOnDemand());
ISVNEditor filterEditor = SVNAmbientDepthFilterEditor.wrap(editor, adminInfo, depthIsSticky);
try {
repos.update(revNumber, target, depth, sendCopyFrom, reporter, SVNCancellableEditor.newInstance(filterEditor, this, getDebugLog()));
} finally {
if (repos2[0] != null) {
repos2[0].closeSession();
}
}
long targetRevision = editor.getTargetRevision();
if (targetRevision >= 0) {
if ((depth == SVNDepth.INFINITY || depth == SVNDepth.UNKNOWN) && !isIgnoreExternals()) {
handleExternals(wcAccess, adminInfo.getAnchor().getRoot(), adminInfo.getOldExternals(), adminInfo.getNewExternals(), adminInfo.getDepths(), url, reposRoot, depth, false, true);
}
dispatchEvent(SVNEventFactory.createSVNEvent(adminInfo.getTarget().getRoot(), SVNNodeKind.NONE, null, targetRevision, SVNEventAction.UPDATE_COMPLETED, null, null, null, reporter
.getReportedFilesCount(), reporter.getTotalFilesCount()));
}
return targetRevision;
} finally {
wcAccess.close();
sleepForTimeStamp();
}
}
/**
* Sets whether working copies should be locked on demand or not during an
* update process.
* <p>
* For additional description, please, refer to
* {@link #isUpdateLocksOnDemand()}.
*
* @param locksOnDemand
* <span class="javakeyword">true</span> to make update lock a
* working copy tree on demand only (for those subdirectories
* only which will be changed by update)
*/
public void setUpdateLocksOnDemand(boolean locksOnDemand) {
myIsUpdateLocksOnDemand = locksOnDemand;
}
/**
* Says whether the entire working copy should be locked while updating or
* not.
* <p/>
* If this method returns <span class="javakeyword">false</span>, then the
* working copy will be closed for all paths involved in the update.
* Otherwise only those working copy subdirectories will be locked, which
* will be either changed by the update or which contain deleted files that
* should be restored during the update; all other versioned subdirectories
* than won't be touched by the update will remain opened for read only
* access without locking.
* <p/>
* Locking working copies on demand is intended to improve update
* performance for large working copies because even a no-op update on a
* huge working copy always locks the entire tree by default. And locking a
* working copy tree means opening special lock files for privileged access
* for all subdirectories involved. This makes an update process work
* slower. Locking wc on demand feature suggests such a workaround to
* enhance update performance.
*
* @return <span class="javakeyword">true</span> when locking wc on demand
*/
public boolean isUpdateLocksOnDemand() {
return myIsUpdateLocksOnDemand;
}
/**
* Updates the Working Copy item to mirror a new URL.
* <p>
* As a revision <b>SVNRevision</b>'s pre-defined constant fields can be
* used. For example, to update the Working Copy to the latest revision of
* the repository use {@link SVNRevision#HEAD HEAD}.
* <p>
* Calling this method is equivalent to
* <code>doSwitch(file, url, SVNRevision.UNDEFINED, revision, recursive)</code>.
*
* @param file
* the Working copy item to be switched
* @param url
* the repository location as a target against which the item
* will be switched
* @param revision
* the desired revision of the repository target
* @param recursive
* if <span class="javakeyword">true</span> and <code>file</code>
* is a directory then the entire tree will be updated, otherwise
* if <span class="javakeyword">false</span> - only items located
* immediately in the directory itself
* @return the revision number to which <code>file</code> was updated to
* @throws SVNException
* @deprecated use
* {@link #doSwitch(File,SVNURL,SVNRevision,SVNRevision,SVNDepth,boolean,boolean)}
* instead
*/
public long doSwitch(File file, SVNURL url, SVNRevision revision, boolean recursive) throws SVNException {
return doSwitch(file, url, SVNRevision.UNDEFINED, revision, SVNDepth.getInfinityOrFilesDepth(recursive), false, false);
}
/**
* Updates the Working Copy item to mirror a new URL.
* <p>
* As a revision <b>SVNRevision</b>'s pre-defined constant fields can be
* used. For example, to update the Working Copy to the latest revision of
* the repository use {@link SVNRevision#HEAD HEAD}.
*
* @param file
* the Working copy item to be switched
* @param url
* the repository location as a target against which the item
* will be switched
* @param pegRevision
* a revision in which <code>file</code> is first looked up in
* the repository
* @param revision
* the desired revision of the repository target
* @param recursive
* if <span class="javakeyword">true</span> and <code>file</code>
* is a directory then the entire tree will be updated, otherwise
* if <span class="javakeyword">false</span> - only items located
* immediately in the directory itself
* @return the revision number to which <code>file</code> was updated to
* @throws SVNException
* @deprecated use
* {@link #doSwitch(File,SVNURL,SVNRevision,SVNRevision,SVNDepth,boolean,boolean)}
* instead
*/
public long doSwitch(File file, SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean recursive) throws SVNException {
return doSwitch(file, url, pegRevision, revision, SVNDepth.getInfinityOrFilesDepth(recursive), false, false);
}
/**
* @param file
* @param url
* @param pegRevision
* @param revision
* @param recursive
* @param force
* @return actual revision number
* @throws SVNException
* @deprecated use
* {@link #doSwitch(File,SVNURL,SVNRevision,SVNRevision,SVNDepth,boolean,boolean)}
* instead
*/
public long doSwitch(File file, SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean recursive, boolean force) throws SVNException {
return doSwitch(file, url, pegRevision, revision, SVNDepth.getInfinityOrFilesDepth(recursive), force, false);
}
/**
* Switches working tree <code>path</code> to <code>url</code>\
* <code>pegRevision</code> at <code>revision</code>.
* <p/>
* Summary of purpose: this is normally used to switch a working directory
* over to another line of development, such as a branch or a tag. Switching
* an existing working directory is more efficient than checking out
* <code>url</code> from scratch.
* <p/>
* <code>revision</code> must represent a valid revision number (
* {@link SVNRevision#getNumber()} >= 0), or date (
* {@link SVNRevision#getDate()} != <span class="javakeyword">true</span>),
* or be equal to {@link SVNRevision#HEAD}. If <code>revision</code> does
* not meet these requirements, an exception with the error code
* {@link SVNErrorCode#CLIENT_BAD_REVISION} is thrown.
* <p/>
* If <code>depth</code> is {@link SVNDepth#INFINITY}, switches fully
* recursively. Else if it is {@link SVNDepth#IMMEDIATES}, switches
* <code>path</code> and its file children (if any), and switches
* subdirectories but does not update them. Else if {@link SVNDepth#FILES},
* switches just file children, ignoring subdirectories completely. Else if
* {@link SVNDepth#EMPTY}, switches just <code>path</code> and touches
* nothing underneath it.
* <p/>
* If <code>depthIsSticky</code> is set and <code>depth</code> is not
* {@link SVNDepth#UNKNOWN}, then in addition to switching <code>path</code>
* , also sets its sticky ambient depth value to <code>depth</code>.
* <p/>
* If externals are {@link #isIgnoreExternals() ignored}, doesn't process
* externals definitions as part of this operation.
* <p/>
* If <code>allowUnversionedObstructions</code> is <span
* class="javakeyword">true</span> then the switch tolerates existing
* unversioned items that obstruct added paths. Only obstructions of the
* same type (file or dir) as the added item are tolerated. The text of
* obstructing files is left as-is, effectively treating it as a user
* modification after the switch. Working properties of obstructing items
* are set equal to the base properties. If
* <code>allowUnversionedObstructions</code> is <span
* class="javakeyword">false</span> then the switch will abort if there are
* any unversioned obstructing items.
* <p/>
* If the caller's {@link ISVNEventHandler} is non-<span
* class="javakeyword">null</span>, it is invoked for paths affected by the
* switch, and also for files restored from text-base. Also
* {@link ISVNEventHandler#checkCancelled()} will be used at various places
* during the switch to check whether the caller wants to stop the switch.
* <p/>
* This operation requires repository access (in case the repository is not
* on the same machine, network connection is established).
*
* @param path
* the Working copy item to be switched
* @param url
* the repository location as a target against which the item
* will be switched
* @param pegRevision
* a revision in which <code>path</code> is first looked up in
* the repository
* @param revision
* the desired revision of the repository target
* @param depth
* tree depth to update
* @param allowUnversionedObstructions
* flag that allows tolerating unversioned items during update
* @param depthIsSticky
* flag that controls whether the requested depth should be
* written into the working copy
* @return value of the revision to which the working copy was actually
* switched
* @throws SVNException
* @since 1.2, SVN 1.5
*/
public long doSwitch(File path, SVNURL url, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, boolean allowUnversionedObstructions, boolean depthIsSticky) throws SVNException {
return doSwitchImpl(null, path, url, pegRevision, revision, depth, allowUnversionedObstructions, depthIsSticky);
}
/**
* Checks out a Working Copy from a repository.
* <p>
* If the destination path (<code>dstPath</code>) is <span
* class="javakeyword">null</span> then the last component of
* <code>url</code> is used for the local directory name.
* <p>
* As a revision <b>SVNRevision</b>'s pre-defined constant fields can be
* used. For example, to check out a Working Copy at the latest revision of
* the repository use {@link SVNRevision#HEAD HEAD}.
*
* @param url
* a repository location from where a Working Copy will be
* checked out
* @param dstPath
* the local path where the Working Copy will be placed
* @param pegRevision
* the revision at which <code>url</code> will be firstly seen in
* the repository to make sure it's the one that is needed
* @param revision
* the desired revision of the Working Copy to be checked out
* @param recursive
* if <span class="javakeyword">true</span> and <code>url</code>
* is a directory then the entire tree will be checked out,
* otherwise if <span class="javakeyword">false</span> - only
* items located immediately in the directory itself
* @return the revision number of the Working Copy
* @throws SVNException
* <code>url</code> refers to a file, not a directory;
* <code>dstPath</code> already exists but it is a file, not a
* directory; <code>dstPath</code> already exists and is a
* versioned directory but has a different URL (repository
* location against which the directory is controlled)
* @deprecated use
* {@link #doCheckout(SVNURL,File,SVNRevision,SVNRevision,SVNDepth,boolean)}
* instead
*/
public long doCheckout(SVNURL url, File dstPath, SVNRevision pegRevision, SVNRevision revision, boolean recursive) throws SVNException {
return doCheckout(url, dstPath, pegRevision, revision, SVNDepth.fromRecurse(recursive), false);
}
/**
* @param url
* @param dstPath
* @param pegRevision
* @param revision
* @param recursive
* @param force
* @return actual revision number
* @throws SVNException
* @deprecated use
* {@link #doCheckout(SVNURL,File,SVNRevision,SVNRevision,SVNDepth,boolean)}
* instead
*/
public long doCheckout(SVNURL url, File dstPath, SVNRevision pegRevision, SVNRevision revision, boolean recursive, boolean force) throws SVNException {
return doCheckout(url, dstPath, pegRevision, revision, SVNDepth.fromRecurse(recursive), force);
}
/**
* Checks out a working copy of <code>url</code> at <code>revision</code>,
* looked up at <code>pegRevision</code>, using <code>dstPath</code> as the
* root directory of the newly checked out working copy.
* <p/>
* If <code>pegRevision</code> is {@link SVNRevision#UNDEFINED}, then it
* defaults to {@link SVNRevision#HEAD}.
* <p/>
* <code>revision</code> must represent a valid revision number (
* {@link SVNRevision#getNumber()} >= 0), or date (
* {@link SVNRevision#getDate()} != <span class="javakeyword">true</span>),
* or be equal to {@link SVNRevision#HEAD}. If <code>revision</code> does
* not meet these requirements, an exception with the error code
* {@link SVNErrorCode#CLIENT_BAD_REVISION} is thrown.
* <p/>
* If <code>depth</code> is {@link SVNDepth#INFINITY}, checks out fully
* recursively. Else if it is {@link SVNDepth#IMMEDIATES}, checks out
* <code>url</code> and its immediate entries (subdirectories will be
* present, but will be at depth {@link SVNDepth#EMPTY} themselves); else
* {@link SVNDepth#FILES}, checks out <code>url</code> and its file entries,
* but no subdirectories; else if {@link SVNDepth#EMPTY}, checks out
* <code>url</code> as an empty directory at that depth, with no entries
* present.
* <p/>
* If <code>depth</code> is {@link SVNDepth#UNKNOWN}, then behave as if for
* {@link SVNDepth#INFINITY}, except in the case of resuming a previous
* checkout of <code>dstPath</code> (i.e., updating), in which case uses the
* depth of the existing working copy.
* <p/>
* If externals are {@link #isIgnoreExternals() ignored}, doesn't process
* externals definitions as part of this operation.
* <p/>
* If <code>allowUnversionedObstructions</code> is <span
* class="javakeyword">true</span> then the checkout tolerates existing
* unversioned items that obstruct added paths from <code>url</code>. Only
* obstructions of the same type (file or dir) as the added item are
* tolerated. The text of obstructing files is left as-is, effectively
* treating it as a user modification after the checkout. Working properties
* of obstructing items are set equal to the base properties. If
* <code>allowUnversionedObstructions</code> is <span
* class="javakeyword">false</span> then the checkout will abort if there
* are any unversioned obstructing items.
* <p/>
* If the caller's {@link ISVNEventHandler} is non-<span
* class="javakeyword">null</span>, it is invoked as the checkout processes.
* Also {@link ISVNEventHandler#checkCancelled()} will be used at various
* places during the checkout to check whether the caller wants to stop the
* checkout.
* <p/>
* This operation requires repository access (in case the repository is not
* on the same machine, network connection is established).
*
* @param url
* a repository location from where a Working Copy will be
* checked out
* @param dstPath
* the local path where the Working Copy will be placed
* @param pegRevision
* the revision at which <code>url</code> will be firstly seen in
* the repository to make sure it's the one that is needed
* @param revision
* the desired revision of the Working Copy to be checked out
* @param depth
* tree depth
* @param allowUnversionedObstructions
* flag that allows tolerating unversioned items during
* @return value of the revision actually checked out from the repository
* @throws SVNException
* <ul>
* <li/>{@link SVNErrorCode#UNSUPPORTED_FEATURE} - if <code>url
* </code> refers to a file rather than a directory <li/>
* {@link SVNErrorCode#RA_ILLEGAL_URL} - if <code>url</code>
* does not exist
* </ul>
* @since 1.2, SVN 1.5
*/
public long doCheckout(SVNURL url, File dstPath, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, boolean allowUnversionedObstructions) throws SVNException {
if (dstPath == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_FILENAME, "Checkout destination path can not be NULL");
SVNErrorManager.error(err, SVNLogType.WC);
}
pegRevision = pegRevision == null ? SVNRevision.UNDEFINED : pegRevision;
if (!revision.isValid() && pegRevision.isValid()) {
revision = pegRevision;
}
if (!revision.isValid()) {
revision = SVNRevision.HEAD;
}
SVNRepository repos = createRepository(url, null, null, pegRevision, revision, null);
url = repos.getLocation();
long revNumber = getRevisionNumber(revision, repos, null);
SVNNodeKind targetNodeKind = repos.checkPath("", revNumber);
if (targetNodeKind == SVNNodeKind.FILE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "URL ''{0}'' refers to a file, not a directory", url);
SVNErrorManager.error(err, SVNLogType.WC);
} else if (targetNodeKind == SVNNodeKind.NONE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' doesn''t exist", url);
SVNErrorManager.error(err, SVNLogType.WC);
}
String uuid = repos.getRepositoryUUID(true);
SVNURL repositoryRoot = repos.getRepositoryRoot(true);
long result = -1;
depth = depth == null ? SVNDepth.UNKNOWN : depth;
SVNWCAccess wcAccess = createWCAccess();
SVNFileType kind = SVNFileType.getType(dstPath);
if (kind == SVNFileType.NONE) {
depth = depth == SVNDepth.UNKNOWN ? SVNDepth.INFINITY : depth;
SVNAdminAreaFactory.createVersionedDirectory(dstPath, url, repositoryRoot, uuid, revNumber, depth);
result = update(dstPath, revision, depth, allowUnversionedObstructions, true, false);
} else if (kind == SVNFileType.DIRECTORY) {
int formatVersion = SVNAdminAreaFactory.checkWC(dstPath, true);
if (formatVersion != 0) {
SVNAdminArea adminArea = wcAccess.open(dstPath, false, 0);
SVNEntry rootEntry = adminArea.getEntry(adminArea.getThisDirName(), false);
wcAccess.closeAdminArea(dstPath);
if (rootEntry.getSVNURL() != null && url.equals(rootEntry.getSVNURL())) {
result = update(dstPath, revision, depth, allowUnversionedObstructions, true, false);
} else {
String message = "''{0}'' is already a working copy for a different URL";
if (rootEntry.isIncomplete()) {
message += "; perform update to complete it";
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, message, dstPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
} else {
depth = depth == SVNDepth.UNKNOWN ? SVNDepth.INFINITY : depth;
SVNAdminAreaFactory.createVersionedDirectory(dstPath, url, repositoryRoot, uuid, revNumber, depth);
result = update(dstPath, revision, depth, allowUnversionedObstructions, true, false);
}
} else {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NODE_KIND_CHANGE, "''{0}'' already exists and is not a directory", dstPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
return result;
}
/**
* Exports a clean directory or single file from a repository.
* <p>
* If <code>eolStyle</code> is not <span class="javakeyword">null</span>
* then it should denote a specific End-Of-Line marker for the files to be
* exported. Significant values for <code>eolStyle</code> are:
* <ul>
* <li>"CRLF" (Carriage Return Line Feed) - this causes files to contain
* '\r\n' line ending sequences for EOL markers, regardless of the operating
* system in use (for instance, this EOL marker is used by software on the
* Windows platform).
* <li>"LF" (Line Feed) - this causes files to contain '\n' line ending
* sequences for EOL markers, regardless of the operating system in use (for
* instance, this EOL marker is used by software on the Unix platform).
* <li>"CR" (Carriage Return) - this causes files to contain '\r' line
* ending sequences for EOL markers, regardless of the operating system in
* use (for instance, this EOL marker was used by software on older
* Macintosh platforms).
* <li>"native" - this causes files to contain the EOL markers that are
* native to the operating system on which SVNKit is run.
* </ul>
*
* @param url
* a repository location from where the unversioned
* directory/file will be exported
* @param dstPath
* the local path where the repository items will be exported to
* @param pegRevision
* the revision at which <code>url</code> will be firstly seen in
* the repository to make sure it's the one that is needed
* @param revision
* the desired revision of the directory/file to be exported
* @param eolStyle
* a string that denotes a specific End-Of-Line character;
* @param force
* <span class="javakeyword">true</span> to fore the operation
* even if there are local files with the same names as those in
* the repository (local ones will be replaced)
* @param recursive
* if <span class="javakeyword">true</span> and <code>url</code>
* is a directory then the entire tree will be exported,
* otherwise if <span class="javakeyword">false</span> - only
* items located immediately in the directory itself
* @return the revision number of the exported directory/file
* @throws SVNException
* @deprecated use
* {@link #doExport(SVNURL,File,SVNRevision,SVNRevision,String,boolean,SVNDepth)}
*/
public long doExport(SVNURL url, File dstPath, SVNRevision pegRevision, SVNRevision revision, String eolStyle, boolean force, boolean recursive) throws SVNException {
return doExport(url, dstPath, pegRevision, revision, eolStyle, force, SVNDepth.fromRecurse(recursive));
}
/**
* Exports the contents of a subversion repository into a 'clean' directory
* (meaning a directory with no administrative directories).
* <p/>
* <code>pegRevision</code> is the revision where the path is first looked
* up. If <code>pegRevision</code> is {@link SVNRevision#UNDEFINED}, then it
* defaults to {@link SVNRevision#HEAD}.
* <p/>
* If externals are {@link #isIgnoreExternals() ignored}, doesn't process
* externals definitions as part of this operation.
* <p/>
* <code>eolStyle</code> allows you to override the standard eol marker on
* the platform you are running on. Can be either "LF", "CR" or "CRLF" or
* <span class="javakeyword">null</span>. If <span
* class="javakeyword">null</span> will use the standard eol marker. Any
* other value will cause an exception with the error code
* {@link SVNErrorCode#IO_UNKNOWN_EOL} error to be returned.
* <p>
* If <code>depth</code> is {@link SVNDepth#INFINITY}, exports fully
* recursively. Else if it is {@link SVNDepth#IMMEDIATES}, exports
* <code>url</code> and its immediate children (if any), but with
* subdirectories empty and at{@link SVNDepth#EMPTY}. Else if
* {@link SVNDepth#FILES}, exports <code>url</code> and its immediate file
* children (if any) only. If <code>depth</code> is {@link SVNDepth#EMPTY},
* then exports exactly <code>url</code> and none of its children.
*
* @param url
* repository url to export from
* @param dstPath
* path to export to
* @param pegRevision
* the revision at which <code>url</code> will be firstly seen in
* the repository to make sure it's the one that is needed
* @param revision
* the desired revision of the directory/file to be exported
* @param eolStyle
* a string that denotes a specific End-Of-Line character
* @param overwrite
* if <span class="javakeyword">true</span>, will cause the
* export to overwrite files or directories
* @param depth
* tree depth
* @return value of the revision actually exported
* @throws SVNException
* @since 1.2, SVN 1.5
*/
public long doExport(SVNURL url, File dstPath, SVNRevision pegRevision, SVNRevision revision, String eolStyle, boolean overwrite, SVNDepth depth) throws SVNException {
long[] revNum = {
SVNRepository.INVALID_REVISION
};
SVNRepository repository = createRepository(url, null, null, pegRevision, revision, revNum);
long exportedRevision = doRemoteExport(repository, revNum[0], dstPath, eolStyle, overwrite, depth);
dispatchEvent(SVNEventFactory.createSVNEvent(null, SVNNodeKind.NONE, null, exportedRevision, SVNEventAction.UPDATE_COMPLETED, null, null, null));
return exportedRevision;
}
/**
* Exports a clean directory or single file from eihter a source Working
* Copy or a repository.
* <p>
* How this method works:
* <ul>
* <li>If <code>revision</code> is different from {@link SVNRevision#BASE
* BASE}, {@link SVNRevision#WORKING WORKING}, {@link SVNRevision#COMMITTED
* COMMITTED}, {@link SVNRevision#UNDEFINED UNDEFINED} - then the repository
* origin of <code>srcPath</code> will be exported (what is done by "remote"
* {@link #doExport(SVNURL,File,SVNRevision,SVNRevision,String,boolean,boolean)
* doExport()}).
* <li>In other cases a clean unversioned copy of <code>srcPath</code> -
* either a directory or a single file - is exported to <code>dstPath</code>.
* </ul>
* <p>
* If <code>eolStyle</code> is not <span class="javakeyword">null</span>
* then it should denote a specific End-Of-Line marker for the files to be
* exported. Significant values for <code>eolStyle</code> are:
* <ul>
* <li>"CRLF" (Carriage Return Line Feed) - this causes files to contain
* '\r\n' line ending sequences for EOL markers, regardless of the operating
* system in use (for instance, this EOL marker is used by software on the
* Windows platform).
* <li>"LF" (Line Feed) - this causes files to contain '\n' line ending
* sequences for EOL markers, regardless of the operating system in use (for
* instance, this EOL marker is used by software on the Unix platform).
* <li>"CR" (Carriage Return) - this causes files to contain '\r' line
* ending sequences for EOL markers, regardless of the operating system in
* use (for instance, this EOL marker was used by software on older
* Macintosh platforms).
* <li>"native" - this causes files to contain the EOL markers that are
* native to the operating system on which SVNKit is run.
* </ul>
*
* @param srcPath
* a repository location from where the unversioned
* directory/file will be exported
* @param dstPath
* the local path where the repository items will be exported to
* @param pegRevision
* the revision at which <code>url</code> will be firstly seen in
* the repository to make sure it's the one that is needed
* @param revision
* the desired revision of the directory/file to be exported
* @param eolStyle
* a string that denotes a specific End-Of-Line character;
* @param force
* <span class="javakeyword">true</span> to fore the operation
* even if there are local files with the same names as those in
* the repository (local ones will be replaced)
* @param recursive
* if <span class="javakeyword">true</span> and <code>url</code>
* is a directory then the entire tree will be exported,
* otherwise if <span class="javakeyword">false</span> - only
* items located immediately in the directory itself
* @return the revision number of the exported directory/file
* @throws SVNException
* @deprecated use
* {@link #doExport(File,File,SVNRevision,SVNRevision,String,boolean,SVNDepth)}
*/
public long doExport(File srcPath, final File dstPath, SVNRevision pegRevision, SVNRevision revision, String eolStyle, final boolean force, boolean recursive) throws SVNException {
return doExport(srcPath, dstPath, pegRevision, revision, eolStyle, force, SVNDepth.fromRecurse(recursive));
}
/**
* Exports the contents of either a subversion repository or a subversion
* working copy into a 'clean' directory (meaning a directory with no
* administrative directories).
* <p/>
* <code>pegRevision</code> is the revision where the path is first looked
* up when exporting from a repository. If <code>pegRevision</code> is
* {@link SVNRevision#UNDEFINED}, then it defaults to
* {@link SVNRevision#WORKING}.
* <p/>
* If <code>revision</code> is one of:
* <ul>
* <li/>{@link SVNRevision#BASE}
* <li/>{@link SVNRevision#WORKING}
* <li/>{@link SVNRevision#COMMITTED}
* <li/>{@link SVNRevision#UNDEFINED}
* </ul>
* then local export is performed. Otherwise exporting from the repository.
* <p/>
* If externals are {@link #isIgnoreExternals() ignored}, doesn't process
* externals definitions as part of this operation.
* <p/>
* <code>eolStyle</code> allows you to override the standard eol marker on
* the platform you are running on. Can be either "LF", "CR" or "CRLF" or
* <span class="javakeyword">null</span>. If <span
* class="javakeyword">null</span> will use the standard eol marker. Any
* other value will cause an exception with the error code
* {@link SVNErrorCode#IO_UNKNOWN_EOL} error to be returned.
* <p>
* If <code>depth</code> is {@link SVNDepth#INFINITY}, exports fully
* recursively. Else if it is {@link SVNDepth#IMMEDIATES}, exports
* <code>srcPath</code> and its immediate children (if any), but with
* subdirectories empty and at{@link SVNDepth#EMPTY}. Else if
* {@link SVNDepth#FILES}, exports <code>srcPath</code> and its immediate
* file children (if any) only. If <code>depth</code> is
* {@link SVNDepth#EMPTY}, then exports exactly <code>srcPath</code> and
* none of its children.
*
* @param srcPath
* working copy path
* @param dstPath
* path to export to
* @param pegRevision
* the revision at which <code>url</code> will be firstly seen in
* the repository to make sure it's the one that is needed
* @param revision
* the desired revision of the directory/file to be exported;
* used only when exporting from a repository
* @param eolStyle
* a string that denotes a specific End-Of-Line character
* @param overwrite
* if <span class="javakeyword">true</span>, will cause the
* export to overwrite files or directories
* @param depth
* tree depth
* @return value of the revision actually exported
* @throws SVNException
* @since 1.2, SVN 1.5
*/
public long doExport(File srcPath, final File dstPath, SVNRevision pegRevision, SVNRevision revision, String eolStyle, final boolean overwrite, SVNDepth depth) throws SVNException {
long exportedRevision = -1;
if (revision != SVNRevision.BASE && revision != SVNRevision.WORKING && revision != SVNRevision.COMMITTED && revision != SVNRevision.UNDEFINED) {
SVNRepository repository = createRepository(null, srcPath, null, pegRevision, revision, null);
long revisionNumber = getRevisionNumber(revision, repository, srcPath);
exportedRevision = doRemoteExport(repository, revisionNumber, dstPath, eolStyle, overwrite, depth);
} else {
if (revision == SVNRevision.UNDEFINED) {
revision = SVNRevision.WORKING;
}
copyVersionedDir(srcPath, dstPath, revision, eolStyle, overwrite, depth);
}
dispatchEvent(SVNEventFactory.createSVNEvent(null, SVNNodeKind.NONE, null, exportedRevision, SVNEventAction.UPDATE_COMPLETED, null, null, null));
return exportedRevision;
}
/**
* Substitutes the beginning part of a Working Copy's URL with a new one.
* <p>
* When a repository root location or a URL schema is changed the old URL of
* the Working Copy which starts with <code>oldURL</code> should be
* substituted for a new URL beginning - <code>newURL</code>.
*
* @param dst
* a Working Copy item's path
* @param oldURL
* the old beginning part of the repository's URL that should be
* overwritten
* @param newURL
* a new beginning part for the repository location that will
* overwrite <code>oldURL</code>
* @param recursive
* if <span class="javakeyword">true</span> and <code>dst</code>
* is a directory then the entire tree will be relocated,
* otherwise if <span class="javakeyword">false</span> - only
* <code>dst</code> itself
* @throws SVNException
*/
public void doRelocate(File dst, SVNURL oldURL, SVNURL newURL, boolean recursive) throws SVNException {
SVNWCAccess wcAccess = createWCAccess();
try {
SVNAdminArea adminArea = wcAccess.probeOpen(dst, true, recursive ? SVNWCAccess.INFINITE_DEPTH : 0);
String name = dst.equals(adminArea.getRoot()) ? adminArea.getThisDirName() : dst.getName();
String from = oldURL.toString();
String to = newURL.toString();
if (from.endsWith("/")) {
from = from.substring(0, from.length() - 1);
}
if (to.endsWith("/")) {
to = to.substring(0, to.length() - 1);
}
doRelocate(adminArea, name, from, to, recursive, new SVNHashMap());
} finally {
wcAccess.close();
}
}
/**
* Canonicalizes all urls in the specified Working Copy.
*
* @param dst
* a WC path
* @param omitDefaultPort
* if <span class="javakeyword">true</span> then removes all port
* numbers from urls which equal to default ones, otherwise does
* not
* @param recursive
* recurses an operation
* @throws SVNException
*/
public void doCanonicalizeURLs(File dst, boolean omitDefaultPort, boolean recursive) throws SVNException {
SVNWCAccess wcAccess = createWCAccess();
try {
SVNAdminAreaInfo adminAreaInfo = wcAccess.openAnchor(dst, true, recursive ? SVNWCAccess.INFINITE_DEPTH : 0);
SVNAdminArea target = adminAreaInfo.getTarget();
SVNEntry entry = wcAccess.getEntry(dst, false);
String name = target.getThisDirName();
if (entry != null && entry.isFile()) {
name = entry.getName();
}
doCanonicalizeURLs(adminAreaInfo, target, name, omitDefaultPort, recursive);
if (recursive && !isIgnoreExternals()) {
for (Iterator externals = adminAreaInfo.getNewExternals().keySet().iterator(); externals.hasNext();) {
String path = (String) externals.next();
String external = (String) adminAreaInfo.getNewExternals().get(path);
SVNExternal[] exts = SVNExternal.parseExternals(path, external);
File owner = new File(adminAreaInfo.getAnchor().getRoot(), path);
for (int i = 0; i < exts.length; i++) {
File externalFile = new File(owner, exts[i].getPath());
try {
doCanonicalizeURLs(externalFile, omitDefaultPort, true);
} catch (SVNCancelException e) {
throw e;
} catch (SVNException e) {
getDebugLog().logFine(SVNLogType.WC, e);
}
}
}
}
} finally {
wcAccess.close();
}
}
/**
* Sets whether keywords must be expanded during an export operation.
*
* @param expand
* <span class="javakeyword">true</span> to expand; otherwise
* <span class="javakeyword">false</span>
* @since 1.3
*/
public void setExportExpandsKeywords(boolean expand) {
myIsExportExpandsKeywords = expand;
}
/**
* Says whether keywords expansion during export operations is turned on or
* not.
*
* @return <span class="javakeyword">true</span> if expanding keywords;
* <span class="javakeyword">false</span> otherwise
* @since 1.3
*/
public boolean isExportExpandsKeywords() {
return myIsExportExpandsKeywords;
}
private void copyVersionedDir(File from, File to, SVNRevision revision, String eolStyle, boolean force, SVNDepth depth) throws SVNException {
SVNWCAccess wcAccess = createWCAccess();
SVNAdminArea adminArea = wcAccess.probeOpen(from, false, 0);
SVNEntry entry = null;
try {
entry = wcAccess.getVersionedEntry(from, false);
} catch (SVNException svne) {
wcAccess.close();
throw svne;
}
if (revision == SVNRevision.WORKING && entry.isScheduledForDeletion()) {
return;
}
if (revision != SVNRevision.WORKING && entry.isScheduledForAddition()) {
return;
}
if (entry.isDirectory()) {
boolean dirCreated = to.mkdirs();
if (!to.exists() || to.isFile()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot create directory ''{0}''", to);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (!dirCreated && to.isDirectory() && !force) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "''{0}'' already exists and will not be owerwritten unless forced", to);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (entry.isDirectory() && entry.isThisDir()) {
for (Iterator ents = adminArea.entries(false); ents.hasNext();) {
SVNEntry childEntry = (SVNEntry) ents.next();
if (childEntry.isDirectory()) {
if (adminArea.getThisDirName().equals(childEntry.getName())) {
continue;
} else if (depth == SVNDepth.INFINITY) {
File childTo = new File(to, childEntry.getName());
File childFrom = new File(from, childEntry.getName());
copyVersionedDir(childFrom, childTo, revision, eolStyle, force, depth);
}
} else if (childEntry.isFile()) {
File childTo = new File(to, childEntry.getName());
copyVersionedFile(childTo, adminArea, childEntry.getName(), revision, eolStyle);
}
}
}
if (!isIgnoreExternals() && depth == SVNDepth.INFINITY && entry.getDepth() == SVNDepth.INFINITY) {
SVNVersionedProperties properties = adminArea.getProperties(adminArea.getThisDirName());
String externalsValue = properties.getStringPropertyValue(SVNProperty.EXTERNALS);
if (externalsValue != null) {
SVNExternal[] externals = SVNExternal.parseExternals(adminArea.getRoot().getAbsolutePath(), externalsValue);
for (int i = 0; i < externals.length; i++) {
SVNExternal info = externals[i];
File srcPath = new File(adminArea.getRoot(), info.getPath());
File dstPath = new File(to, info.getPath());
if (SVNPathUtil.getSegmentsCount(info.getPath()) > 1) {
if (!dstPath.getParentFile().exists() && !dstPath.getParentFile().mkdirs()) {
SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.CLIENT_IS_DIRECTORY, "Could not create directory ''{0}''", dstPath.getParentFile()), SVNLogType.WC);
}
}
copyVersionedDir(srcPath, dstPath, revision, eolStyle, force, SVNDepth.INFINITY);
}
}
}
} else if (entry.isFile()) {
copyVersionedFile(to, adminArea, entry.getName(), revision, eolStyle);
}
wcAccess.close();
}
private void copyVersionedFile(File dstPath, SVNAdminArea adminArea, String fileName, SVNRevision revision, String eol) throws SVNException {
SVNEntry entry = adminArea.getEntry(fileName, false);
if (revision == SVNRevision.WORKING && entry.isScheduledForDeletion()) {
return;
}
if (revision != SVNRevision.WORKING && entry.isScheduledForAddition()) {
return;
}
boolean modified = false;
SVNVersionedProperties props = null;
long timestamp;
if (revision != SVNRevision.WORKING) {
props = adminArea.getBaseProperties(fileName);
} else {
props = adminArea.getProperties(fileName);
modified = adminArea.hasTextModifications(fileName, false);
}
boolean special = props.getPropertyValue(SVNProperty.SPECIAL) != null;
boolean executable = props.getPropertyValue(SVNProperty.EXECUTABLE) != null;
String keywords = props.getStringPropertyValue(SVNProperty.KEYWORDS);
String charsetProp = props.getStringPropertyValue(SVNProperty.CHARSET);
String mimeType = props.getStringPropertyValue(SVNProperty.MIME_TYPE);
String charset = SVNTranslator.getCharset(charsetProp, mimeType, adminArea.getFile(fileName).getPath(), getOptions());
byte[] eols = eol != null ? SVNTranslator.getEOL(eol, getOptions()) : null;
if (eols == null) {
eol = props.getStringPropertyValue(SVNProperty.EOL_STYLE);
eols = SVNTranslator.getEOL(eol, getOptions());
}
if (modified && !special) {
timestamp = SVNFileUtil.getFileLastModified(adminArea.getFile(fileName));
} else {
timestamp = SVNDate.parseDateAsMilliseconds(entry.getCommittedDate());
}
Map keywordsMap = null;
if (keywords != null) {
String rev = Long.toString(entry.getCommittedRevision());
String author;
if (modified) {
author = "(local)";
rev += "M";
} else {
author = entry.getAuthor();
}
keywordsMap = SVNTranslator.computeKeywords(keywords, entry.getURL(), entry.getRepositoryRoot(), author, entry.getCommittedDate(), rev, getOptions());
}
File srcFile = revision == SVNRevision.WORKING ? adminArea.getFile(fileName) : adminArea.getBaseFile(fileName, false);
SVNFileType fileType = SVNFileType.getType(srcFile);
if (fileType == SVNFileType.SYMLINK && revision == SVNRevision.WORKING) {
File tmpBaseFile = adminArea.getBaseFile(fileName, true);
try {
SVNTranslator.translate(srcFile, tmpBaseFile, charset, eols, keywordsMap, special, false);
SVNTranslator.translate(tmpBaseFile, dstPath, charset, eols, keywordsMap, special, true);
} finally {
tmpBaseFile.delete();
}
} else {
SVNTranslator.translate(srcFile, dstPath, charset, eols, keywordsMap, special, true);
}
if (executable) {
SVNFileUtil.setExecutable(dstPath, true);
}
if (!special && timestamp > 0) {
SVNFileUtil.setLastModified(dstPath, timestamp);
}
}
private long doRemoteExport(SVNRepository repository, final long revNumber, File dstPath, String eolStyle, boolean force, SVNDepth depth) throws SVNException {
SVNNodeKind dstKind = repository.checkPath("", revNumber);
if (dstKind == SVNNodeKind.DIR) {
SVNExportEditor editor = new SVNExportEditor(this, repository.getLocation().toString(), dstPath, force, eolStyle, isExportExpandsKeywords(), repository.getRepositoryRoot(true).toString(), getOptions());
repository.update(revNumber, null, depth, false, new ISVNReporterBaton() {
public void report(ISVNReporter reporter) throws SVNException {
reporter.setPath("", null, revNumber, SVNDepth.INFINITY, true);
reporter.finishReport();
}
}, SVNCancellableEditor.newInstance(editor, this, getDebugLog()));
SVNFileType fileType = SVNFileType.getType(dstPath);
if (fileType == SVNFileType.NONE) {
editor.openRoot(revNumber);
}
if (!isIgnoreExternals() && depth == SVNDepth.INFINITY) {
Map externals = editor.getCollectedExternals();
handleExternals(null, dstPath, Collections.EMPTY_MAP, externals, Collections.EMPTY_MAP, repository.getLocation(), repository.getRepositoryRoot(true), depth, true, true);
}
} else if (dstKind == SVNNodeKind.FILE) {
String url = repository.getLocation().toString();
String repositoryRoot = isExportExpandsKeywords() ? repository.getRepositoryRoot(true).toString() : null;
if (dstPath.isDirectory()) {
dstPath = new File(dstPath, SVNEncodingUtil.uriDecode(SVNPathUtil.tail(url)));
}
if (dstPath.exists()) {
if (!force) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, "Path ''{0}'' already exists", dstPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
} else {
dstPath.getParentFile().mkdirs();
}
SVNProperties properties = new SVNProperties();
OutputStream os = null;
File tmpFile = SVNFileUtil.createUniqueFile(dstPath.getParentFile(), ".export", ".tmp", false);
try {
os = SVNFileUtil.openFileForWriting(tmpFile);
try {
repository.getFile("", revNumber, properties, new SVNCancellableOutputStream(os, this));
} finally {
SVNFileUtil.closeFile(os);
}
if (force && dstPath.exists()) {
SVNFileUtil.deleteAll(dstPath, this);
}
if (!isExportExpandsKeywords()) {
properties.put(SVNProperty.MIME_TYPE, "application/octet-stream");
}
String mimeType = properties.getStringValue(SVNProperty.MIME_TYPE);
boolean binary = SVNProperty.isBinaryMimeType(mimeType);
String charset = SVNTranslator.getCharset(properties.getStringValue(SVNProperty.CHARSET), mimeType, url, getOptions());
Map keywords = SVNTranslator.computeKeywords(properties.getStringValue(SVNProperty.KEYWORDS), url, repositoryRoot, properties.getStringValue(SVNProperty.LAST_AUTHOR), properties
.getStringValue(SVNProperty.COMMITTED_DATE), properties.getStringValue(SVNProperty.COMMITTED_REVISION), getOptions());
byte[] eols = null;
if (SVNProperty.EOL_STYLE_NATIVE.equals(properties.getStringValue(SVNProperty.EOL_STYLE))) {
eols = SVNTranslator.getEOL(eolStyle != null ? eolStyle : properties.getStringValue(SVNProperty.EOL_STYLE), getOptions());
} else if (properties.containsName(SVNProperty.EOL_STYLE)) {
eols = SVNTranslator.getEOL(properties.getStringValue(SVNProperty.EOL_STYLE), getOptions());
}
if (binary) {
charset = null;
eols = null;
keywords = null;
}
SVNTranslator.translate(tmpFile, dstPath, charset, eols, keywords, properties.getStringValue(SVNProperty.SPECIAL) != null, true);
} finally {
SVNFileUtil.deleteFile(tmpFile);
}
if (properties.getStringValue(SVNProperty.EXECUTABLE) != null) {
SVNFileUtil.setExecutable(dstPath, true);
}
dispatchEvent(SVNEventFactory.createSVNEvent(dstPath, SVNNodeKind.FILE, null, SVNRepository.INVALID_REVISION, SVNEventAction.UPDATE_ADD, null, null, null));
} else {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' doesn't exist", repository.getLocation());
SVNErrorManager.error(err, SVNLogType.WC);
}
return revNumber;
}
private void doCanonicalizeURLs(SVNAdminAreaInfo adminAreaInfo, SVNAdminArea adminArea, String name, boolean omitDefaultPort, boolean recursive) throws SVNException {
boolean save = false;
checkCancelled();
if (!adminArea.getThisDirName().equals(name)) {
SVNEntry entry = adminArea.getEntry(name, true);
save = canonicalizeEntry(entry, omitDefaultPort);
adminArea.getWCProperties(name).setPropertyValue(SVNProperty.WC_URL, null);
if (save) {
adminArea.saveEntries(false);
}
return;
}
if (!isIgnoreExternals()) {
SVNPropertyValue externalsValue = adminArea.getProperties(adminArea.getThisDirName()).getPropertyValue(SVNProperty.EXTERNALS);
if (externalsValue != null) {
String ownerPath = adminArea.getRelativePath(adminAreaInfo.getAnchor());
String externals = externalsValue == null ? null : externalsValue.getString();
adminAreaInfo.addExternal(ownerPath, externals, externals);
if (externalsValue != null) {
externalsValue = SVNPropertyValue.create(canonicalizeExtenrals(externals, omitDefaultPort));
adminArea.getProperties(adminArea.getThisDirName()).setPropertyValue(SVNProperty.EXTERNALS, externalsValue);
}
}
}
SVNEntry rootEntry = adminArea.getEntry(adminArea.getThisDirName(), true);
save = canonicalizeEntry(rootEntry, omitDefaultPort);
adminArea.getWCProperties(adminArea.getThisDirName()).setPropertyValue(SVNProperty.WC_URL, null);
for (Iterator ents = adminArea.entries(true); ents.hasNext();) {
SVNEntry entry = (SVNEntry) ents.next();
if (adminArea.getThisDirName().equals(entry.getName())) {
continue;
}
checkCancelled();
if (recursive && entry.isDirectory() && (entry.isScheduledForAddition() || !entry.isDeleted()) && !entry.isAbsent()) {
SVNAdminArea childArea = adminArea.getWCAccess().retrieve(adminArea.getFile(entry.getName()));
if (childArea != null) {
doCanonicalizeURLs(adminAreaInfo, childArea, "", omitDefaultPort, recursive);
}
}
save |= canonicalizeEntry(entry, omitDefaultPort);
SVNVersionedProperties properties = adminArea.getWCProperties(entry.getName());
if (properties != null) {
properties.setPropertyValue(SVNProperty.WC_URL, null);
}
}
if (save) {
adminArea.saveEntries(true);
}
}
public static String canonicalizeExtenrals(String externals, boolean omitDefaultPort) throws SVNException {
if (externals == null) {
return null;
}
StringBuffer canonicalized = new StringBuffer();
for (StringTokenizer lines = new StringTokenizer(externals, "\r\n", true); lines.hasMoreTokens();) {
String line = lines.nextToken();
if (line.trim().length() == 0 || line.trim().startsWith("#") || line.indexOf('\r') >= 0 || line.indexOf('\n') >= 0) {
canonicalized.append(line);
continue;
}
String[] tokens = line.split("[ \t]");
int index = tokens.length - 1;
SVNURL url = null;
if (index >= 1) {
try {
url = SVNURL.parseURIEncoded(tokens[index]);
} catch (SVNException e) {
url = null;
}
}
SVNURL canonicalURL = canonicalizeURL(url, omitDefaultPort);
if (canonicalURL == null) {
canonicalized.append(line);
} else {
canonicalized.append(tokens[0]);
canonicalized.append(' ');
if (index == 2) {
canonicalized.append(tokens[1]);
canonicalized.append(' ');
}
canonicalized.append(canonicalURL.toString());
}
}
return canonicalized.toString();
}
private static boolean canonicalizeEntry(SVNEntry entry, boolean omitDefaultPort) throws SVNException {
boolean updated = false;
SVNURL root = canonicalizeURL(entry.getRepositoryRootURL(), omitDefaultPort);
if (root != null) {
updated |= entry.setRepositoryRootURL(root);
}
SVNURL url = canonicalizeURL(entry.getSVNURL(), omitDefaultPort);
if (url != null) {
updated |= entry.setURL(url.toString());
}
SVNURL copyFrom = canonicalizeURL(entry.getCopyFromSVNURL(), omitDefaultPort);
if (copyFrom != null) {
updated |= entry.setCopyFromURL(copyFrom.toString());
}
return updated;
}
public static SVNURL canonicalizeURL(SVNURL url, boolean omitDefaultPort) throws SVNException {
if (url == null || url.getPort() <= 0) {
return null;
}
int defaultPort = SVNURL.getDefaultPortNumber(url.getProtocol());
if (defaultPort <= 0) {
return null;
}
if (omitDefaultPort) {
if (url.hasPort() && url.getPort() == defaultPort) {
return SVNURL.create(url.getProtocol(), url.getUserInfo(), url.getHost(), -1, url.getPath(), false);
}
} else if (!url.hasPort()) {
return SVNURL.create(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), false);
}
return null;
}
private void handleExternals(SVNWCAccess wcAccess, File root, Map oldExternals, Map newExternals, Map depths, SVNURL fromURL, SVNURL rootURL, SVNDepth requestedDepth, boolean isExport,
boolean updateUnchanged) throws SVNException {
Set diff = new SVNHashSet();
if (oldExternals != null) {
diff.addAll(oldExternals.keySet());
}
if (newExternals != null) {
diff.addAll(newExternals.keySet());
}
for (Iterator diffPaths = diff.iterator(); diffPaths.hasNext();) {
String diffPath = (String) diffPaths.next();
SVNDepth ambientDepth = depths == Collections.EMPTY_MAP ? SVNDepth.INFINITY : (SVNDepth) depths.get(diffPath);
if (ambientDepth == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT, "Traversal of ''{0}'' found no ambient depth", diffPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (!ambientDepth.isRecursive() || !requestedDepth.isRecursive()) {
continue;
}
String oldValue = (String) oldExternals.get(diffPath);
String newValue = (String) newExternals.get(diffPath);
SVNExternal[] previous = oldValue != null ? SVNExternal.parseExternals(diffPath, oldValue) : null;
SVNExternal[] current = newValue != null ? SVNExternal.parseExternals(diffPath, newValue) : null;
Map oldParsedExternals = new LinkedHashMap();
Map newParsedExternals = new LinkedHashMap();
for (int i = 0; current != null && i < current.length; i++) {
newParsedExternals.put(current[i].getPath(), current[i]);
}
for (int i = 0; previous != null && i < previous.length; i++) {
oldParsedExternals.put(previous[i].getPath(), previous[i]);
}
ExternalDiff externalDiff = new ExternalDiff();
externalDiff.isExport = isExport;
externalDiff.isUpdateUnchanged = updateUnchanged;
externalDiff.rootURL = rootURL;
for (Iterator paths = oldParsedExternals.keySet().iterator(); paths.hasNext();) {
String path = (String) paths.next();
externalDiff.oldExternal = (SVNExternal) oldParsedExternals.get(path);
externalDiff.newExternal = (SVNExternal) newParsedExternals.get(path);
externalDiff.owner = new File(root, diffPath);
if (!isExport) {
externalDiff.ownerURL = getOwnerURL(externalDiff.owner);
}
if (externalDiff.ownerURL == null) {
externalDiff.ownerURL = fromURL.appendPath(diffPath, false);
}
handleExternalItemChange(wcAccess, externalDiff.oldExternal.getPath(), externalDiff);
}
for (Iterator paths = newParsedExternals.keySet().iterator(); paths.hasNext();) {
String path = (String) paths.next();
if (!oldParsedExternals.containsKey(path)) {
externalDiff.oldExternal = null;
externalDiff.newExternal = (SVNExternal) newParsedExternals.get(path);
externalDiff.owner = new File(root, diffPath);
if (!isExport) {
externalDiff.ownerURL = getOwnerURL(externalDiff.owner);
}
if (externalDiff.ownerURL == null) {
externalDiff.ownerURL = fromURL.appendPath(diffPath, false);
}
handleExternalItemChange(wcAccess, externalDiff.newExternal.getPath(), externalDiff);
}
}
}
}
private SVNURL getOwnerURL(File root) {
if (root != null && SVNFileType.getType(root) == SVNFileType.DIRECTORY) {
SVNWCAccess access = createWCAccess();
try {
access.open(root, false, 0);
SVNEntry entry = access.getVersionedEntry(root, false);
if (entry != null) {
return entry.getSVNURL();
}
} catch (SVNException e) {
e.printStackTrace();
} finally {
if (access != null) {
try {
access.close();
} catch (SVNException e) {
}
}
}
}
return null;
}
private void handleExternalItemChange(SVNWCAccess access, String targetDir, ExternalDiff externalDiff) throws SVNException {
try {
handleExternalChange(access, targetDir, externalDiff);
} catch (SVNException svne) {
File target = new File(externalDiff.owner, targetDir);
SVNEvent event = SVNEventFactory.createSVNEvent(target, SVNNodeKind.UNKNOWN, null, SVNRepository.INVALID_REVISION, SVNEventAction.FAILED_EXTERNAL, SVNEventAction.UPDATE_EXTERNAL, svne
.getErrorMessage(), null);
dispatchEvent(event);
}
}
/**
* oldURL is null when externals is added: jsvn ps svn:externals "path URL"
* . jsvn up . newURL is null when external is deleted: jsvn pd
* svn:externals . jsvn up . Also newURL or oldURL could be null, when
* external property is added or removed by update itself (someone else has
* changed it). For instance, oldURL is always null during checkout or
* export operation.
*/
private void handleExternalChange(SVNWCAccess access, String targetDir, ExternalDiff externalDiff) throws SVNException {
File target = new File(externalDiff.owner, targetDir);
SVNURL oldURL = null;
SVNURL newURL = null;
String externalDefinition = null;
if (externalDiff.oldExternal != null && !externalDiff.isExport) {
oldURL = externalDiff.oldExternal.resolveURL(externalDiff.rootURL, externalDiff.ownerURL);
externalDefinition = externalDiff.oldExternal.getRawValue();
}
SVNRevision externalRevision = SVNRevision.UNDEFINED;
SVNRevision externalPegRevision = SVNRevision.UNDEFINED;
if (externalDiff.newExternal != null) {
newURL = externalDiff.newExternal.resolveURL(externalDiff.rootURL, externalDiff.ownerURL);
externalRevision = externalDiff.newExternal.getRevision();
externalPegRevision = externalDiff.newExternal.getPegRevision();
externalDefinition = externalDiff.newExternal.getRawValue();
}
if (oldURL == null && newURL == null) {
return;
}
SVNRevision[] revs = getExternalsHandler().handleExternal(target, newURL, externalRevision, externalPegRevision, externalDefinition, SVNRevision.UNDEFINED);
if (revs == null) {
SVNEvent event = SVNEventFactory.createSVNEvent(target, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.SKIP, SVNEventAction.UPDATE_EXTERNAL, null, null);
dispatchEvent(event);
return;
}
externalRevision = revs.length > 0 && revs[0] != null ? revs[0] : externalRevision;
externalPegRevision = revs.length > 1 && revs[1] != null ? revs[1] : externalPegRevision;
SVNRepository repository = null;
SVNNodeKind kind = null;
SVNURL reposRootURL = null;
if (newURL != null) {
long[] rev = {
SVNRepository.INVALID_REVISION
};
repository = createRepository(newURL, null, null, externalPegRevision, externalRevision, rev);
reposRootURL = repository.getRepositoryRoot(true);
kind = repository.checkPath("", rev[0]);
if (kind == SVNNodeKind.NONE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' at revision {1} doesn''t exist", new Object[] {
repository.getLocation(), String.valueOf(rev[0])
});
SVNErrorManager.error(err, SVNLogType.WC);
}
if (kind != SVNNodeKind.DIR && kind != SVNNodeKind.FILE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' at revision {1} is not a file or a directory", new Object[] {
repository.getLocation(), String.valueOf(rev[0])
});
SVNErrorManager.error(err, SVNLogType.WC);
}
}
try {
setEventPathPrefix("path");
if (oldURL == null) {
if (kind == SVNNodeKind.DIR) {
target.mkdirs();
dispatchEvent(SVNEventFactory.createSVNEvent(target, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.UPDATE_EXTERNAL, null, null, null));
if (externalDiff.isExport) {
doExport(newURL, target, externalPegRevision, externalRevision, null, true, SVNDepth.INFINITY);
} else {
doCheckout(newURL, target, externalPegRevision, externalRevision, SVNDepth.INFINITY, false);
}
} else if (kind == SVNNodeKind.FILE) {
dispatchEvent(SVNEventFactory.createSVNEvent(target, SVNNodeKind.FILE, null, SVNRepository.INVALID_REVISION, SVNEventAction.UPDATE_EXTERNAL, null, null, null));
if (externalDiff.isExport) {
boolean ignoreExternals = isIgnoreExternals();
setIgnoreExternals(true);
doExport(newURL, target, externalPegRevision, externalRevision, null, false, SVNDepth.INFINITY);
setIgnoreExternals(ignoreExternals);
} else {
switchFileExternal(access, target, newURL, externalPegRevision, externalRevision, reposRootURL);
}
}
} else if (newURL == null) {
SVNWCAccess wcAccess = createWCAccess();
SVNAdminArea area = wcAccess.open(target, true, SVNWCAccess.INFINITE_DEPTH);
SVNException error = null;
try {
area.removeFromRevisionControl(area.getThisDirName(), true, false);
} catch (SVNException svne) {
error = svne;
}
if (error == null || error.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) {
try {
wcAccess.close();
} catch (SVNException svne) {
error = error == null ? svne : error;
}
}
if (error != null && error.getErrorMessage().getErrorCode() != SVNErrorCode.WC_LEFT_LOCAL_MOD) {
throw error;
}
} else if (externalDiff.isUpdateUnchanged || !externalDiff.compareExternals(oldURL, newURL)) {
if (kind == SVNNodeKind.DIR) {
SVNFileType fileType = SVNFileType.getType(target);
boolean empty = false;
if (fileType == SVNFileType.DIRECTORY) {
File[] children = target.listFiles();
if (children != null && children.length == 0) {
empty = true;
}
}
if (fileType == SVNFileType.DIRECTORY && !empty) {
dispatchEvent(SVNEventFactory.createSVNEvent(target, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.UPDATE_EXTERNAL, null, null, null));
SVNWCAccess wcAccess = createWCAccess();
SVNAdminArea area = wcAccess.open(target, true, 0);
SVNEntry entry = area.getEntry(area.getThisDirName(), false);
wcAccess.close();
String url = entry.getURL();
if (entry != null && entry.getURL() != null) {
if (newURL.toString().equals(url)) {
doUpdate(target, externalRevision, SVNDepth.UNKNOWN, true, false);
return;
} else if (entry.getRepositoryRoot() != null) {
if (!SVNPathUtil.isAncestor(entry.getRepositoryRoot(), newURL.toString())) {
SVNRepository repos = createRepository(newURL, null, null, true);
SVNURL reposRoot = repos.getRepositoryRoot(true);
try {
doRelocate(target, entry.getSVNURL(), reposRoot, true);
} catch (SVNException svne) {
if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_INVALID_RELOCATION
|| svne.getErrorMessage().getErrorCode() == SVNErrorCode.CLIENT_INVALID_RELOCATION) {
deleteExternal(target);
target.mkdirs();
doCheckout(newURL, target, externalPegRevision, externalRevision, SVNDepth.INFINITY, false);
return;
}
throw svne;
}
}
doSwitch(target, newURL, externalPegRevision, externalRevision, SVNDepth.INFINITY, false, true);
return;
}
}
deleteExternal(target);
target.mkdirs();
dispatchEvent(SVNEventFactory.createSVNEvent(target, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.UPDATE_EXTERNAL, null, null, null));
doCheckout(newURL, target, externalPegRevision, externalRevision, SVNDepth.INFINITY, false);
return;
}
if (fileType != SVNFileType.DIRECTORY) {
target.mkdirs();
}
dispatchEvent(SVNEventFactory.createSVNEvent(target, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.UPDATE_EXTERNAL, null, null, null));
doCheckout(newURL, target, externalPegRevision, externalRevision, SVNDepth.INFINITY, true);
} else {
dispatchEvent(SVNEventFactory.createSVNEvent(target, SVNNodeKind.FILE, null, SVNRepository.INVALID_REVISION, SVNEventAction.UPDATE_EXTERNAL, null, null, null));
switchFileExternal(access, target, newURL, externalPegRevision, externalRevision, reposRootURL);
}
}
} catch (SVNCancelException cancel) {
throw cancel;
} catch (SVNException e) {
SVNDebugLog.getDefaultLog().logFine(SVNLogType.WC, e);
SVNEvent event = SVNEventFactory.createSVNEvent(target, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.SKIP, SVNEventAction.UPDATE_EXTERNAL, e.getErrorMessage(),
null);
dispatchEvent(event);
} finally {
setEventPathPrefix(null);
}
}
private void switchFileExternal(SVNWCAccess wcAccess, File path, SVNURL url, SVNRevision pegRevision, SVNRevision revision, SVNURL reposRootURL) throws SVNException {
String target = SVNWCManager.getActualTarget(path);
File anchor = "".equals(target) ? path : path.getParentFile();
boolean closeTarget = false;
boolean revertFile = false;
boolean removeFromRevisionControl = false;
boolean unlinkFile = false;
boolean cleanUp = false;
boolean ignoreExternals = isIgnoreExternals();
SVNAdminArea targetArea = null;
try {
try {
targetArea = wcAccess.retrieve(anchor);
} catch (SVNException svne) {
SVNErrorMessage err = svne.getErrorMessage();
if (err.getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
SVNWCAccess targetAccess = SVNWCAccess.newInstance(null);
targetArea = targetAccess.open(anchor, true, 1);
closeTarget = true;
SVNURL dstWCReposRootURL = getReposRoot(anchor, null, SVNRevision.BASE, targetArea, targetAccess);
if (!reposRootURL.equals(dstWCReposRootURL)) {
SVNErrorMessage err1 = SVNErrorMessage.create(SVNErrorCode.RA_REPOS_ROOT_URL_MISMATCH,
"Cannot insert a file external from ''{0}'' into a working copy from a different repository rooted at ''{1}''", new Object[] {
url, dstWCReposRootURL
});
SVNErrorManager.error(err1, SVNLogType.WC);
}
} else {
throw svne;
}
}
if (targetArea.getFormatVersion() < SVNAdminAreaFactory.WC_FORMAT_16) {
dispatchEvent(SVNEventFactory.createSVNEvent(path, SVNNodeKind.FILE, null, SVNRepository.INVALID_REVISION, SVNEventAction.SKIP, SVNEventAction.UPDATE_EXTERNAL, null, null));
return;
}
SVNEntry entry = targetArea.getEntry(target, false);
if (entry != null) {
if (entry.getExternalFilePath() == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED,
"The file external from ''{0}'' cannot overwrite the existing versioned item at ''{1}''", new Object[] {
url, path
});
SVNErrorManager.error(err, SVNLogType.WC);
}
} else {
targetArea.getVersionedEntry(targetArea.getThisDirName(), false);
boolean hasPropConflicts = targetArea.hasPropConflict(targetArea.getThisDirName());
boolean hasTreeConflicts = targetArea.hasTreeConflict(targetArea.getThisDirName());
if (hasPropConflicts || hasTreeConflicts) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_FOUND_CONFLICT, "The file external from ''{0}'' cannot be written to ''{1}'' while ''{2}'' remains in conflict",
new Object[] {
url, path, anchor
});
SVNErrorManager.error(err, SVNLogType.WC);
}
if (!path.exists()) {
SVNFileUtil.createEmptyFile(path);
unlinkFile = true;
}
ISVNEventHandler eventHandler = targetArea.getWCAccess().getEventHandler();
try {
targetArea.getWCAccess().setEventHandler(null);
SVNWCManager.add(path, targetArea, null, SVNRepository.INVALID_REVISION, SVNDepth.INFINITY);
} catch (SVNException svne) {
cleanUp = true;
throw svne;
} finally {
if (eventHandler != null) {
targetArea.getWCAccess().setEventHandler(eventHandler);
}
}
revertFile = true;
try {
targetArea.setFileExternalLocation(target, url, pegRevision, revision, reposRootURL);
} catch (SVNException svne) {
cleanUp = true;
throw svne;
}
}
setIgnoreExternals(true);
try {
doSwitchImpl(targetArea.getWCAccess(), path, url, pegRevision, revision, SVNDepth.EMPTY, false, false);
} catch (SVNException svne) {
cleanUp = true;
throw svne;
}
if (unlinkFile) {
revertFile = false;
removeFromRevisionControl = true;
}
} catch (SVNException svne) {
if (cleanUp) {
if (revertFile) {
SVNWCClient16 wcClient = new SVNWCClient16(getRepositoryPool(), getOptions());
try {
wcClient.doRevert(new File[] {
path
}, SVNDepth.EMPTY, null);
} catch (SVNException svne2) {
}
}
if (removeFromRevisionControl) {
try {
targetArea.removeFromRevisionControl(target, true, false);
} catch (SVNException svne2) {
}
}
if (unlinkFile) {
try {
SVNFileUtil.deleteFile(path);
} catch (SVNException svne2) {
}
}
}
throw svne;
} finally {
setIgnoreExternals(ignoreExternals);
if (closeTarget) {
targetArea.getWCAccess().close();
}
}
}
private void deleteExternal(File external) throws SVNException {
SVNWCAccess wcAccess = createWCAccess();
SVNAdminArea adminArea = wcAccess.open(external, true, SVNWCAccess.INFINITE_DEPTH);
SVNException error = null;
try {
adminArea.removeFromRevisionControl(adminArea.getThisDirName(), true, false);
} catch (SVNException svne) {
getDebugLog().logFine(SVNLogType.WC, svne);
error = svne;
}
if (error == null || error.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) {
wcAccess.close();
}
if (error != null && error.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) {
external.getParentFile().mkdirs();
File newLocation = SVNFileUtil.createUniqueFile(external.getParentFile(), external.getName(), ".OLD", false);
SVNFileUtil.rename(external, newLocation);
} else if (error != null) {
throw error;
}
}
private Map validateRelocateTargetURL(SVNURL targetURL, String expectedUUID, Map validatedURLs, boolean isRoot) throws SVNException {
if (validatedURLs == null) {
return null;
}
for (Iterator targetURLs = validatedURLs.keySet().iterator(); targetURLs.hasNext();) {
SVNURL validatedURL = (SVNURL) targetURLs.next();
if (targetURL.toString().startsWith(validatedURL.toString())) {
if (isRoot && !targetURL.equals(validatedURL)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_INVALID_RELOCATION, "''{0}'' is not the root of the repository", targetURL);
SVNErrorManager.error(err, SVNLogType.WC);
}
String validatedUUID = (String) validatedURLs.get(validatedURL);
if (expectedUUID != null && !expectedUUID.equals(validatedUUID)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_INVALID_RELOCATION, "The repository at ''{0}'' has uuid ''{1}'', but the WC has ''{2}''", new Object[] {
validatedURL, validatedUUID, expectedUUID
});
SVNErrorManager.error(err, SVNLogType.WC);
}
return validatedURLs;
}
}
SVNRepository repos = createRepository(targetURL, null, null, false);
try {
SVNURL actualRoot = repos.getRepositoryRoot(true);
if (isRoot && !targetURL.equals(actualRoot)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_INVALID_RELOCATION, "''{0}'' is not the root of the repository", targetURL);
SVNErrorManager.error(err, SVNLogType.WC);
}
String actualUUID = repos.getRepositoryUUID(true);
if (expectedUUID != null && !expectedUUID.equals(actualUUID)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_INVALID_RELOCATION, "The repository at ''{0}'' has uuid ''{1}'', but the WC has ''{2}''", new Object[] {
targetURL, actualUUID, expectedUUID
});
SVNErrorManager.error(err, SVNLogType.WC);
}
validatedURLs.put(targetURL, actualUUID);
} finally {
repos.closeSession();
}
return validatedURLs;
}
private Map relocateEntry(SVNEntry entry, String from, String to, Map validatedURLs) throws SVNException {
if (entry.getRepositoryRoot() != null) {
String repos = entry.getRepositoryRoot();
if (from.length() > repos.length()) {
String fromPath = from.substring(repos.length());
if (!to.endsWith(fromPath)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_RELOCATION, "Relocate can only change the repository part of an URL");
SVNErrorManager.error(err, SVNLogType.WC);
}
from = repos;
to = to.substring(0, to.length() - fromPath.length());
}
if (repos.startsWith(from)) {
entry.setRepositoryRoot(to + repos.substring(from.length()));
validatedURLs = validateRelocateTargetURL(entry.getRepositoryRootURL(), entry.getUUID(), validatedURLs, true);
}
}
if (entry.getURL() != null && entry.getURL().startsWith(from)) {
entry.setURL(to + entry.getURL().substring(from.length()));
if (entry.getUUID() != null && validatedURLs != null) {
validatedURLs = validateRelocateTargetURL(entry.getSVNURL(), entry.getUUID(), validatedURLs, false);
}
}
if (entry.getCopyFromURL() != null && entry.getCopyFromURL().startsWith(from)) {
entry.setCopyFromURL(to + entry.getCopyFromURL().substring(from.length()));
if (entry.getUUID() != null && validatedURLs != null) {
validatedURLs = validateRelocateTargetURL(entry.getCopyFromSVNURL(), entry.getUUID(), validatedURLs, false);
}
}
return validatedURLs;
}
private Map doRelocate(SVNAdminArea adminArea, String name, String from, String to, boolean recursive, Map validatedURLs) throws SVNException {
SVNEntry entry = adminArea.getEntry(name, true);
if (entry == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (entry.isFile()) {
relocateEntry(entry, from, to, validatedURLs);
SVNPropertiesManager.deleteWCProperties(adminArea, name, false);
adminArea.saveEntries(false);
return validatedURLs;
}
validatedURLs = relocateEntry(entry, from, to, validatedURLs);
SVNWCAccess wcAccess = adminArea.getWCAccess();
for (Iterator entries = adminArea.entries(true); entries.hasNext();) {
SVNEntry childEntry = (SVNEntry) entries.next();
if (adminArea.getThisDirName().equals(childEntry.getName())) {
continue;
}
if (recursive && childEntry.isDirectory() && (childEntry.isScheduledForAddition() || !childEntry.isDeleted()) && !childEntry.isAbsent() && childEntry.getDepth() != SVNDepth.EXCLUDE) {
File childDir = adminArea.getFile(childEntry.getName());
if (wcAccess.isMissing(childDir)) {
continue;
}
SVNAdminArea childArea = wcAccess.retrieve(childDir);
validatedURLs = doRelocate(childArea, childArea.getThisDirName(), from, to, recursive, validatedURLs);
}
validatedURLs = relocateEntry(childEntry, from, to, validatedURLs);
SVNPropertiesManager.deleteWCProperties(adminArea, childEntry.getName(), false);
}
SVNPropertiesManager.deleteWCProperties(adminArea, "", false);
adminArea.saveEntries(false);
return validatedURLs;
}
private static class ExternalDiff {
public SVNExternal oldExternal;
public SVNExternal newExternal;
public File owner;
public SVNURL ownerURL;
public SVNURL rootURL;
public boolean isExport;
public boolean isUpdateUnchanged;
public boolean compareExternals(SVNURL oldURL, SVNURL newURL) {
return oldURL.equals(newURL) && oldExternal.getRevision().equals(newExternal.getRevision()) && oldExternal.getPegRevision().equals(newExternal.getPegRevision());
}
}
}