package org.tmatesoft.svn.core.internal.wc2.ng; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.tmatesoft.sqljet.core.SqlJetException; import org.tmatesoft.sqljet.core.SqlJetTransactionMode; import org.tmatesoft.svn.core.ISVNDirEntryHandler; import org.tmatesoft.svn.core.SVNCancelException; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNDirEntry; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.internal.util.SVNURLUtil; import org.tmatesoft.svn.core.internal.wc.ISVNUpdateEditor; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNEventFactory; import org.tmatesoft.svn.core.internal.wc.SVNExternal; import org.tmatesoft.svn.core.internal.wc.SVNFileListUtil; import org.tmatesoft.svn.core.internal.wc.SVNFileType; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc17.ISVNDirFetcher; import org.tmatesoft.svn.core.internal.wc17.SVNExternalsStore; import org.tmatesoft.svn.core.internal.wc17.SVNReporter17; import org.tmatesoft.svn.core.internal.wc17.SVNUpdateEditor17; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.SVNWCNodeReposInfo; import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb; import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDb; import org.tmatesoft.svn.core.internal.wc17.db.Structure; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.ExternalNodeInfo; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeInfo; import org.tmatesoft.svn.core.internal.wc17.db.SvnExternalFileReporter; import org.tmatesoft.svn.core.internal.wc17.db.SvnExternalUpdateEditor; import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbExternals; import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.RepositoryInfo; import org.tmatesoft.svn.core.io.SVNCapability; import org.tmatesoft.svn.core.io.SVNLocationSegment; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.ISVNConflictHandler; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.SVNConflictChoice; import org.tmatesoft.svn.core.wc.SVNConflictDescription; import org.tmatesoft.svn.core.wc.SVNConflictResult; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNEventAction; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.AbstractSvnUpdate; import org.tmatesoft.svn.core.wc2.SvnOperationFactory; import org.tmatesoft.svn.core.wc2.SvnRelocate; import org.tmatesoft.svn.core.wc2.SvnTarget; import org.tmatesoft.svn.util.SVNLogType; public abstract class SvnNgAbstractUpdate<V, T extends AbstractSvnUpdate<V>> extends SvnNgOperationRunner<V, T> { protected long update(SVNWCContext wcContext, File localAbspath, SVNRevision revision, SVNDepth depth, boolean depthIsSticky, boolean ignoreExternals, boolean allowUnversionedObstructions, boolean addsAsMoodifications, boolean makeParents, boolean innerUpdate, boolean sleepForTimestamp) throws SVNException { assert ! (innerUpdate && makeParents); File lockRootPath = null; File anchor; try { if (makeParents) { File parentPath = localAbspath; List<File> missingParents = new ArrayList<File>(); while(true) { try { lockRootPath = getWcContext().acquireWriteLock(parentPath, !innerUpdate, true); break; } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_WORKING_COPY || SVNFileUtil.getParentFile(parentPath) == null) { throw e; } } parentPath = SVNFileUtil.getParentFile(parentPath); missingParents.add(0, parentPath); } anchor = lockRootPath; for (File missingParent : missingParents) { long revnum = updateInternal( wcContext, missingParent, anchor, revision, SVNDepth.EMPTY, false, ignoreExternals, allowUnversionedObstructions, addsAsMoodifications, sleepForTimestamp, false, getOperation().getOptions().getConflictResolver()); anchor = missingParent; revision = SVNRevision.create(revnum); } } else { anchor = wcContext.acquireWriteLock(localAbspath, !innerUpdate, true); lockRootPath = anchor; } RecordConflictsResolver recordConflictsResolver = new RecordConflictsResolver(); long updateRevision = updateInternal(wcContext, localAbspath, anchor, revision, depth, depthIsSticky, ignoreExternals, allowUnversionedObstructions, addsAsMoodifications, sleepForTimestamp, true, recordConflictsResolver); ISVNConflictHandler conflictResolver = getWcContext().getOptions().getConflictResolver(); if (conflictResolver != null && recordConflictsResolver.hasConflicts()) { for (SVNConflictDescription conflictDescription : recordConflictsResolver.getConflicts()) { getWcContext().resolvedConflict(conflictDescription.getPath(), SVNDepth.UNKNOWN, true, null, true, null); } } return updateRevision; } finally { if (lockRootPath != null) { wcContext.releaseWriteLock(lockRootPath); } } } protected long updateInternal(SVNWCContext wcContext, File localAbspath, File anchorAbspath, SVNRevision revision, SVNDepth depth, boolean depthIsSticky, boolean ignoreExternals, boolean allowUnversionedObstructions, boolean addsAsMoodifications, boolean sleepForTimestamp, boolean notifySummary, ISVNConflictHandler conflictHandler) throws SVNException { if (depth == SVNDepth.UNKNOWN) { depthIsSticky = false; } String target; if (!localAbspath.equals(anchorAbspath)) { target = SVNFileUtil.getFileName(localAbspath); } else { target = ""; } ISVNWCDb.WCDbBaseInfo nodeBaseInfo = wcContext.getNodeBase(anchorAbspath, true, false); File reposRelPath = nodeBaseInfo.reposRelPath; SVNURL reposRootUrl = nodeBaseInfo.reposRootUrl; boolean targetConflicted = false; final SVNURL anchorUrl; if (reposRelPath != null) { anchorUrl = reposRootUrl.appendPath(SVNFileUtil.getFilePath(reposRelPath), false); try { SVNWCContext.ConflictInfo conflictInfo = wcContext.getConflicted(localAbspath, true, true, false); if (conflictInfo.textConflicted || conflictInfo.propConflicted) { targetConflicted = true; } } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_PATH_NOT_FOUND) { throw e; } } } else { anchorUrl = null; } if (anchorUrl == null || targetConflicted) { SVNEvent event = SVNEventFactory.createSVNEvent(localAbspath, SVNNodeKind.UNKNOWN, null, -1, SVNEventAction.SKIP_CONFLICTED, SVNEventAction.UPDATE_SKIP_WORKING_ONLY, null, null); ISVNEventHandler eventHandler = getOperation().getEventHandler(); if (eventHandler != null) { eventHandler.handleEvent(event, ISVNEventHandler.UNKNOWN); } return -1; } boolean croppingTarget = depthIsSticky && depth.compareTo(SVNDepth.INFINITY) < 0; if (croppingTarget) { if (depth == SVNDepth.EXCLUDE) { wcContext.exclude(localAbspath); return SVNWCContext.INVALID_REVNUM; } final SVNNodeKind targetKind = wcContext.readKind(localAbspath, true); if (targetKind == SVNNodeKind.DIR) { wcContext.cropTree(localAbspath, depth); } } String[] preservedExts = getOperation().getOptions().getPreservedConflictFileExtensions(); boolean useCommitTimes = getOperation().getOptions().isUseCommitTimes(); if (notifySummary) { handleEvent(SVNEventFactory.createSVNEvent(localAbspath, SVNNodeKind.NONE, null, -1, SVNEventAction.UPDATE_STARTED, null, null, null, 0, 0)); } boolean cleanCheckout = isEmptyWc(localAbspath, anchorAbspath); SVNRepository repos = getRepositoryAccess().createRepository(anchorUrl, anchorAbspath); boolean serverSupportsDepth = repos.hasCapability(SVNCapability.DEPTH); final SVNReporter17 reporter = new SVNReporter17(localAbspath, wcContext, true, !serverSupportsDepth, depth, getOperation().isUpdateLocksOnDemand(), false, !depthIsSticky, useCommitTimes, null); final long revNumber = getWcContext().getRevisionNumber(revision, null, repos, localAbspath); final SVNURL reposRoot = repos.getRepositoryRoot(true); final Map<File, Map<String, SVNProperties>> inheritableProperties = SvnNgInheritableProperties.getInheritalbeProperites(wcContext, repos, localAbspath, revNumber, depth); final SVNRepository[] repos2 = new SVNRepository[1]; ISVNDirFetcher dirFetcher = new ISVNDirFetcher() { public Map<String, SVNDirEntry> fetchEntries(SVNURL reposRoot, File path) throws SVNException { SVNURL url = SVNWCUtils.join(reposRoot, path); if (repos2[0] == null) { repos2[0] = getRepositoryAccess().createRepository(url, null, false); } else { repos2[0].setLocation(url, false); } final Map<String, SVNDirEntry> entries = new HashMap<String, SVNDirEntry>(); if (repos2[0].checkPath("", revNumber) == SVNNodeKind.DIR) { repos2[0].getDir("", revNumber, null, new ISVNDirEntryHandler() { public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException { if (dirEntry.getName() != null && !"".equals(dirEntry.getName())) { entries.put(dirEntry.getName(), dirEntry); } } }); return entries; } return null; } }; SVNExternalsStore externalsStore = new SVNExternalsStore(); ISVNUpdateEditor editor = SVNUpdateEditor17.createUpdateEditor(wcContext, revNumber, anchorAbspath, target, inheritableProperties, useCommitTimes, null, depth, depthIsSticky, getOperation().isAllowUnversionedObstructions(), true, serverSupportsDepth, cleanCheckout, dirFetcher, externalsStore, preservedExts, conflictHandler); try { repos.update(revNumber, target, depthIsSticky ? depth : SVNDepth.UNKNOWN, false, reporter, editor); } catch(SVNException e) { sleepForTimestamp(); throw e; } finally { ensureNodesMovedToIndex(wcContext.getDb().getSDb(anchorAbspath)); if (repos2[0] != null) { repos2[0].closeSession(); } } long targetRevision = editor.getTargetRevision(); if (targetRevision >= 0) { if ((depth.isRecursive() || croppingTarget) && !getOperation().isIgnoreExternals()) { getWcContext().getDb().gatherExternalDefinitions(localAbspath, externalsStore); handleExternals(externalsStore.getNewExternals(), externalsStore.getDepths(), anchorUrl, localAbspath, reposRoot, depth, false); } if (sleepForTimestamp) { sleepForTimestamp(); } if (notifySummary) { handleEvent(SVNEventFactory.createSVNEvent(localAbspath, SVNNodeKind.NONE, null, targetRevision, SVNEventAction.UPDATE_COMPLETED, null, null, null, reporter.getReportedFilesCount(), reporter.getTotalFilesCount())); } } return targetRevision; } private void ensureNodesMovedToIndex(SVNSqlJetDb sDb) throws SVNException { try { sDb.beginTransaction(SqlJetTransactionMode.WRITE); if (sDb.getDb().getSchema().getIndex("I_NODES_MOVED") == null) { sDb.getDb().createIndex("CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth);"); } sDb.commit(); } catch (SqlJetException e) { sDb.rollback(); SVNSqlJetDb.createSqlJetError(e); } } protected void handleExternals(Map<File, String> newExternals, Map<File, SVNDepth> ambientDepths, SVNURL anchorUrl, File targetAbspath, SVNURL reposRoot, SVNDepth requestedDepth, boolean sleepForTimestamp) throws SVNException { Map<File, File> oldExternals = getWcContext().getDb().getExternalsDefinedBelow(targetAbspath); for (File externalPath : newExternals.keySet()) { String externalDefinition = newExternals.get(externalPath); SVNDepth ambientDepth = SVNDepth.INFINITY; if (ambientDepths != null) { ambientDepth = ambientDepths.get(externalPath); } handleExternalsChange(reposRoot, externalPath, externalDefinition, oldExternals, ambientDepth, requestedDepth); } for(File oldExternalPath : oldExternals.keySet()) { File definingAbsPath = oldExternals.get(oldExternalPath); try { handleExternalItemRemoval(definingAbsPath, oldExternalPath); } catch (SVNCancelException cancel) { throw cancel; } catch (SVNException e) { handleEvent(SVNEventFactory.createSVNEvent(oldExternalPath, SVNNodeKind.NONE, null, -1, SVNEventAction.FAILED_EXTERNAL, SVNEventAction.UPDATE_EXTERNAL_REMOVED, e.getErrorMessage(), null)); } } } private void handleExternalItemRemoval(File definingAbsPath, File localAbsPath) throws SVNException { SVNNodeKind kind = getWcContext().readKind(localAbsPath, false); if (kind == SVNNodeKind.NONE) { SvnWcDbExternals.removeExternalNode(getWcContext(), localAbsPath, definingAbsPath, null); return; } File lockRootAbsPath = null; boolean lockedHere = getWcContext().getDb().isWCLockOwns(localAbsPath, false); if (!lockedHere) { lockRootAbsPath = getWcContext().acquireWriteLock(localAbsPath, false, true); } SVNErrorMessage err = null; try { SvnWcDbExternals.removeExternal(getWcContext(), definingAbsPath, localAbsPath); } catch (SVNException e) { err = e.getErrorMessage(); } handleEvent(SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.NONE, null, -1, SVNEventAction.UPDATE_EXTERNAL_REMOVED, SVNEventAction.UPDATE_EXTERNAL_REMOVED, err, null, 1, 1)); if (lockRootAbsPath != null) { try { getWcContext().releaseWriteLock(lockRootAbsPath); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_LOCKED) { throw e; } } } if (err != null && err.getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) { err = null; } if (err != null) { SVNErrorManager.error(err, SVNLogType.WC); } } private void handleExternalsChange(SVNURL reposRoot, File externalPath, String externalDefinition, Map<File, File> oldExternals, SVNDepth ambientDepth, SVNDepth requestedDepth) throws SVNException { if ((requestedDepth.compareTo(SVNDepth.INFINITY) < 0 && requestedDepth != SVNDepth.UNKNOWN) || ambientDepth.compareTo(SVNDepth.INFINITY) < 0 && requestedDepth.compareTo(SVNDepth.INFINITY) < 0) { return; } if (externalDefinition != null) { SVNExternal[] externals = SVNExternal.parseExternals(externalPath, externalDefinition); SVNURL url = getWcContext().getNodeUrl(externalPath); for (int i = 0; i < externals.length; i++) { File targetAbsPath = SVNFileUtil.createFilePath(externalPath, externals[i].getPath()); File oldExternalDefiningPath = oldExternals.get(targetAbsPath); try { handleExternalItemChange(reposRoot, externalPath, url, targetAbsPath, oldExternalDefiningPath, externals[i]); } catch (SVNCancelException cancel) { throw cancel; } catch (SVNException e) { handleEvent(SVNEventFactory.createSVNEvent(targetAbsPath, SVNNodeKind.NONE, null, -1, SVNEventAction.FAILED_EXTERNAL, SVNEventAction.UPDATE_EXTERNAL, e.getErrorMessage(), null)); } if (oldExternalDefiningPath != null) { oldExternals.remove(targetAbsPath); } } } } private void handleExternalItemChange(SVNURL rootUrl, File parentPath, SVNURL parentUrl, File localAbsPath, File oldDefiningPath, SVNExternal newItem) throws SVNException { assert newItem != null; assert rootUrl != null && parentUrl != null; SVNURL newUrl = newItem.resolveURL(rootUrl, parentUrl); newUrl = SvnTarget.fromURL(newUrl).getURL(); SVNRevision externalRevision = newItem.getRevision(); SVNRevision externalPegRevision = newItem.getPegRevision(); if (getOperation().getExternalsHandler() != null) { SVNRevision[] revs = getOperation().getExternalsHandler().handleExternal(localAbsPath, newUrl, externalRevision, externalPegRevision, newItem.getRawValue(), SVNRevision.UNDEFINED); if (revs == null) { handleEvent(SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.DIR, null, SVNRepository.INVALID_REVISION, SVNEventAction.SKIP, SVNEventAction.UPDATE_EXTERNAL, null, null)); return; } externalRevision = revs.length > 0 && revs[0] != null ? revs[0] : externalRevision; externalPegRevision = revs.length > 1 && revs[1] != null ? revs[1] : externalPegRevision; } Structure<RepositoryInfo> repositoryInfo = getRepositoryAccess().createRepositoryFor(SvnTarget.fromURL(newUrl), externalRevision, externalPegRevision, null); SVNRepository repository = repositoryInfo.<SVNRepository>get(RepositoryInfo.repository); long externalRevnum = repositoryInfo.lng(RepositoryInfo.revision); repositoryInfo.release(); String repositoryUUID = repository.getRepositoryUUID(true); SVNURL repositoryRoot = repository.getRepositoryRoot(true); SVNNodeKind externalKind = repository.checkPath("", externalRevnum); if (externalKind == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' at revision {1} doesn''t exist", repository.getLocation(), externalRevnum); SVNErrorManager.error(err, SVNLogType.WC); } if (externalKind != SVNNodeKind.DIR && externalKind != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' at revision {1} is not a file or a directory", repository.getLocation(), externalRevnum); SVNErrorManager.error(err, SVNLogType.WC); } SVNNodeKind localKind = externalKind; handleEvent(SVNEventFactory.createSVNEvent(localAbsPath, externalKind, null, -1, SVNEventAction.UPDATE_EXTERNAL, null, null, null, 0, 0)); if (oldDefiningPath == null) { SVNFileUtil.ensureDirectoryExists(SVNFileUtil.getParentFile(localAbsPath)); } if (localKind == SVNNodeKind.DIR) { switchDirExternal(localAbsPath, newUrl, externalRevision, externalPegRevision, parentPath); } else if (localKind == SVNNodeKind.FILE) { if (!repositoryRoot.equals(rootUrl)) { SVNWCNodeReposInfo localReposInfo = getWcContext().getNodeReposInfo(parentPath); SVNURL localReposRootUrl = localReposInfo.reposRootUrl; String localReposUuid = localReposInfo.reposUuid; String externalRepositoryPath = SVNURLUtil.getRelativeURL(repositoryRoot, newUrl, false); if (localReposUuid == null || localReposRootUrl == null || externalRepositoryPath == null || !localReposUuid.equals(repositoryUUID)) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Unsupported external: url of " + "file external ''{0}'' is not in repository ''{0}''", newUrl, rootUrl); SVNErrorManager.error(errorMessage, SVNLogType.WC); } newUrl = localReposRootUrl.appendPath(externalRepositoryPath, false); Structure<RepositoryInfo> repositoryInfoStructure = getRepositoryAccess().createRepositoryFor(SvnTarget.fromURL(newUrl), newItem.getRevision(), newItem.getPegRevision(), null); repository = repositoryInfoStructure.get(RepositoryInfo.repository); externalRevnum = repositoryInfoStructure.lng(RepositoryInfo.revision); } switchFileExternal(localAbsPath, newUrl, externalPegRevision, externalRevision, parentPath, repository, externalRevnum, repository.getRepositoryRoot(true)); } else { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.ASSERTION_FAIL); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } private void switchDirExternal(File localAbsPath, SVNURL url, SVNRevision revision, SVNRevision pegRevision, File definingPath) throws SVNException { SVNFileType fileKind = SVNFileType.getType(localAbsPath); SVNWCDb.DirParsedInfo parsed = ((SVNWCDb) (getWcContext().getDb())).parseDir(localAbsPath, SVNSqlJetDb.Mode.ReadOnly); int workingCopyFormat = parsed.wcDbDir.getWCRoot().getFormat(); if (fileKind == SVNFileType.DIRECTORY) { SVNURL nodeUrl = null; try { nodeUrl = getWcContext().getNodeUrl(localAbsPath); if (nodeUrl != null) { if (url.equals(nodeUrl)) { update(getWcContext(), localAbsPath, revision, SVNDepth.UNKNOWN, false, false, false, true, false, true, false); return; } SVNWCNodeReposInfo nodeRepositoryInfo = getWcContext().getNodeReposInfo(localAbsPath); SVNURL repositoryRootUrl = nodeRepositoryInfo.reposRootUrl; if (nodeRepositoryInfo != null && repositoryRootUrl != null) { if (!SVNURLUtil.isAncestor(repositoryRootUrl, url)) { SVNRepository svnRepository = getRepositoryAccess().createRepository(url, null, true); SVNURL repositoryRoot = svnRepository.getRepositoryRoot(true); SvnRelocate relocate = getOperation().getOperationFactory().createRelocate(); relocate.setFromUrl(repositoryRootUrl); relocate.setToUrl(repositoryRoot); relocate.setSingleTarget(SvnTarget.fromFile(localAbsPath)); try { relocate.run(); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_INVALID_RELOCATION || e.getErrorMessage().getErrorCode() == SVNErrorCode.CLIENT_INVALID_RELOCATION) { relegateExternal(localAbsPath, url, revision, pegRevision, definingPath, fileKind, workingCopyFormat); return; } throw e; } repositoryRootUrl = repositoryRoot; } doSwitch(localAbsPath, url, revision, pegRevision, SVNDepth.INFINITY, true, false, false, true, false); getWcContext().getDb().registerExternal(definingPath, localAbsPath, SVNNodeKind.DIR, repositoryRootUrl, nodeRepositoryInfo.reposUuid, SVNFileUtil.createFilePath(SVNPathUtil.getPathAsChild(repositoryRootUrl.getPath(), url.getPath())), SVNWCContext.INVALID_REVNUM, SVNWCContext.INVALID_REVNUM); return; } } } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_PATH_NOT_FOUND) { throw e; } } } relegateExternal(localAbsPath, url, revision, pegRevision, definingPath, fileKind, workingCopyFormat); } private void relegateExternal(File localAbsPath, SVNURL url, SVNRevision revision, SVNRevision pegRevision, File definingPath, SVNFileType fileKind, int targetWorkingCopyFormat) throws SVNException { if (fileKind == SVNFileType.DIRECTORY) { getWcContext().acquireWriteLock(localAbsPath, false, false); relegateExternalDir(definingPath, localAbsPath); } else { SVNFileUtil.ensureDirectoryExists(localAbsPath); } checkout(url, localAbsPath, pegRevision, revision, SVNDepth.INFINITY, false, false, false, targetWorkingCopyFormat); SVNWCNodeReposInfo nodeRepositoryInfo = getWcContext().getNodeReposInfo(localAbsPath); getWcContext().getDb().registerExternal(definingPath, localAbsPath, SVNNodeKind.DIR, nodeRepositoryInfo.reposRootUrl, nodeRepositoryInfo.reposUuid, SVNFileUtil.createFilePath(SVNPathUtil.getPathAsChild(nodeRepositoryInfo.reposRootUrl.getPath(), url.getPath())), SVNWCContext.INVALID_REVNUM, SVNWCContext.INVALID_REVNUM); } private void relegateExternalDir(File wriAbsPath, File localAbsPath) throws SVNException { try { SvnWcDbExternals.removeExternal(getWcContext(), wriAbsPath, localAbsPath); } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) { File oldName = SVNFileUtil.createUniqueFile(localAbsPath.getParentFile(), localAbsPath.getName(), ".OLD", false); SVNFileUtil.deleteFile(oldName); SVNFileUtil.rename(localAbsPath, oldName); return; } throw e; } } private void switchFileExternal(File localAbsPath, SVNURL url, SVNRevision pegRevision, SVNRevision revision, File defDirAbspath, SVNRepository repository, long repositoryRevision, SVNURL reposRootUrl) throws SVNException { File dirAbspath = SVNFileUtil.getParentFile(localAbsPath); boolean lockedHere = getWcContext().getDb().isWCLockOwns(dirAbspath, false); if (!lockedHere) { File wcRoot = getWcContext().getDb().getWCRoot(dirAbspath); String rootPath = wcRoot.getAbsolutePath().replace(File.separatorChar, '/'); String defPath = defDirAbspath.getAbsolutePath().replace(File.separatorChar, '/'); if (!SVNPathUtil.isAncestor(rootPath, defPath)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_BAD_PATH, "Cannot insert a file external defined on ''{0}'' into the working copy ''{1}''", defDirAbspath, wcRoot); SVNErrorManager.error(err, SVNLogType.WC); } } SVNNodeKind kind = SVNNodeKind.NONE; SVNNodeKind externalKind = SVNNodeKind.NONE; try { kind = getWcContext().readKind(localAbsPath, false); Structure<ExternalNodeInfo> externalInfo = null; try { externalInfo = SvnWcDbExternals.readExternal(getWcContext(), localAbsPath, localAbsPath, ExternalNodeInfo.kind); ISVNWCDb.SVNWCDbKind eKind = externalInfo.<ISVNWCDb.SVNWCDbKind>get(ExternalNodeInfo.kind); if (eKind != null) { externalKind = eKind.toNodeKind(); } } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_PATH_NOT_FOUND) { throw e; } } finally { if (externalInfo != null) { externalInfo.release(); } } } catch (SVNException e) { if (!lockedHere) { getWcContext().releaseWriteLock(dirAbspath); } throw e; } if (kind != SVNNodeKind.NONE && kind != SVNNodeKind.UNKNOWN) { if (externalKind != SVNNodeKind.FILE) { if (!lockedHere) { getWcContext().releaseWriteLock(dirAbspath); } SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, "The file external from ''{0}'' cannot overwrite the existing versioned item at ''{1}''", url, localAbsPath); SVNErrorManager.error(err, SVNLogType.WC); } } else { SVNNodeKind diskKind = SVNFileType.getNodeKind(SVNFileType.getType(localAbsPath)); if (diskKind == SVNNodeKind.FILE || diskKind == SVNNodeKind.DIR) { if (!lockedHere) { getWcContext().releaseWriteLock(dirAbspath); } SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_PATH_FOUND, "The file external ''{0}'' can not be created because the node exists.", localAbsPath); SVNErrorManager.error(err, SVNLogType.WC); return; } } // do file external update. SvnTarget target = SvnTarget.fromURL(url); Structure<RepositoryInfo> repositoryInfo = getRepositoryAccess().createRepositoryFor(target, revision, pegRevision, dirAbspath); repository = repositoryInfo.<SVNRepository>get(RepositoryInfo.repository); long revnum = repositoryInfo.lng(RepositoryInfo.revision); SVNURL switchUrl = repositoryInfo.<SVNURL>get(RepositoryInfo.url); repositoryInfo.release(); String uuid = repository.getRepositoryUUID(true); String[] preservedExts = getOperation().getOptions().getPreservedConflictFileExtensions(); boolean useCommitTimes = getOperation().getOptions().isUseCommitTimes(); Map<String, SVNProperties> iprops = repository.getInheritedProperties("", revnum, null); if (iprops != null && !iprops.isEmpty()) { iprops = SvnNgInheritableProperties.translateInheritedPropertiesPaths(iprops); } repository.setLocation(SVNURL.parseURIEncoded(SVNPathUtil.removeTail(url.toString())), true); File definitionAbsPath = SVNFileUtil.getParentFile(localAbsPath); ISVNUpdateEditor updateEditor = SvnExternalUpdateEditor.createEditor( getWcContext(), localAbsPath, definitionAbsPath, switchUrl, reposRootUrl, uuid, iprops, useCommitTimes, preservedExts, definitionAbsPath, url, pegRevision, revision); SvnExternalFileReporter reporter = new SvnExternalFileReporter(getWcContext(), localAbsPath, true, useCommitTimes); repository.update(url, revnum, SVNFileUtil.getFileName(localAbsPath), SVNDepth.UNKNOWN, reporter, updateEditor); handleEvent(SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.NONE, null, revnum, SVNEventAction.UPDATE_COMPLETED, null, null, null, 1, 1)); if (!lockedHere) { getWcContext().releaseWriteLock(dirAbspath); } } protected long doSwitch(File localAbsPath, SVNURL switchUrl, SVNRevision revision, SVNRevision pegRevision, SVNDepth depth, boolean depthIsSticky, boolean ignoreExternals, boolean allowUnversionedObstructions, boolean ignoreAncestry, boolean sleepForTimestamp) throws SVNException { File anchor = null; boolean releaseLock = false; try { try { anchor = getWcContext().obtainAnchorPath(localAbsPath, true, true); getWcContext().getDb().obtainWCLock(anchor, -1, false); releaseLock = true; } catch (SVNException e) { if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_LOCKED) { throw e; } releaseLock = false; } getWcContext().getDb().clearDavCacheRecursive(localAbsPath); return switchInternal(localAbsPath, anchor, switchUrl, revision, pegRevision, depth, depthIsSticky, ignoreExternals, allowUnversionedObstructions, ignoreAncestry, sleepForTimestamp); } finally { if (anchor != null && releaseLock) { getWcContext().releaseWriteLock(anchor); } } } protected long switchInternal(File localAbsPath, File anchor, SVNURL switchUrl, SVNRevision revision, SVNRevision pegRevision, SVNDepth depth, boolean depthIsSticky, boolean ignoreExternals, boolean allowUnversionedObstructions, boolean ignoreAncestry, boolean sleepForTimestamp) throws SVNException { if (depth == SVNDepth.UNKNOWN) { depthIsSticky = false; } Structure<NodeInfo> nodeInfo = getWcContext().getDb().readInfo(localAbsPath, NodeInfo.haveWork); try { if (nodeInfo.is(NodeInfo.haveWork)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Cannot switch ''{0}'' because it is not in the repository yet", localAbsPath); SVNErrorManager.error(err, SVNLogType.WC); } } finally { nodeInfo.release(); } String[] preservedExts = getOperation().getOptions().getPreservedConflictFileExtensions(); boolean useCommitTimes = getOperation().getOptions().isUseCommitTimes(); String target; if (!localAbsPath.equals(anchor)) { target = SVNFileUtil.getFileName(localAbsPath); } else { target = ""; } final SVNURL anchorUrl = getWcContext().getNodeUrl(anchor); if (anchorUrl == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "Directory ''{0}'' has no URL", anchor); SVNErrorManager.error(err, SVNLogType.WC); } if (depthIsSticky && depth.compareTo(SVNDepth.INFINITY) < 0) { if (depth == SVNDepth.EXCLUDE) { getWcContext().exclude(localAbsPath); return SVNWCContext.INVALID_REVNUM; } final SVNNodeKind targetKind = getWcContext().readKind(localAbsPath, true); if (targetKind == SVNNodeKind.DIR) { getWcContext().cropTree(localAbsPath, depth); } } Structure<RepositoryInfo> repositoryInfo = getRepositoryAccess().createRepositoryFor(SvnTarget.fromURL(switchUrl), revision, pegRevision, anchor); SVNRepository repository = repositoryInfo.<SVNRepository>get(RepositoryInfo.repository); final long revnum = repositoryInfo.lng(RepositoryInfo.revision); SVNURL switchRevUrl = repositoryInfo.<SVNURL>get(RepositoryInfo.url); repositoryInfo.release(); SVNURL switchRootUrl = repository.getRepositoryRoot(true); if (!anchorUrl.equals(switchRootUrl) && !anchorUrl.toString().startsWith(switchRootUrl.toString() + "/")) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_SWITCH, "''{0}'' is not the same repository as ''{1}''", anchorUrl, switchRootUrl); SVNErrorManager.error(err, SVNLogType.WC); } if (!ignoreAncestry) { SVNURL targetUrl = getWcContext().getNodeUrl(localAbsPath); long targetRev = getWcContext().getNodeBaseRev(localAbsPath); SVNLocationSegment ancestor = getRepositoryAccess().getYoungestCommonAncestor(switchRevUrl, revnum, targetUrl, targetRev); if (!(ancestor.getPath() != null && ancestor.getStartRevision() >= 0)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, "''{0}'' shares no common ancestry with ''{1}''", switchUrl, localAbsPath); SVNErrorManager.error(err, SVNLogType.WC); } } final Map<File, Map<String, SVNProperties>> wcIprops = new HashMap<File, Map<String,SVNProperties>>(); if (!switchRootUrl.equals(switchRevUrl)) { boolean isWcRoot = getWcContext().checkWCRoot(localAbsPath, false).wcRoot; boolean needsCache = true; if (!isWcRoot) { final SVNURL parentURL = getWcContext().getNodeUrl(SVNFileUtil.getParentFile(localAbsPath)); needsCache = !(parentURL.appendPath(localAbsPath.getName(), false).equals(switchRevUrl)); } if (needsCache) { Map<String, SVNProperties> iprops = repository.getInheritedProperties("", revnum, null); iprops = SvnNgInheritableProperties.translateInheritedPropertiesPaths(iprops); wcIprops.put(localAbsPath, iprops); } } repository.setLocation(anchorUrl, false); boolean serverSupportsDepth = repository.hasCapability(SVNCapability.DEPTH); SVNExternalsStore externalsStore = new SVNExternalsStore(); final SVNRepository[] repos2 = new SVNRepository[1]; ISVNDirFetcher dirFetcher = new ISVNDirFetcher() { public Map<String, SVNDirEntry> fetchEntries(SVNURL reposRoot, File path) throws SVNException { SVNURL url = SVNWCUtils.join(reposRoot, path); if (repos2[0] == null) { repos2[0] = getRepositoryAccess().createRepository(url, null, false); } else { repos2[0].setLocation(url, false); } final Map<String, SVNDirEntry> entries = new HashMap<String, SVNDirEntry>(); SVNNodeKind kind = repos2[0].checkPath("", revnum); if (kind == SVNNodeKind.DIR) { repos2[0].getDir("", revnum, null, new ISVNDirEntryHandler() { public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException { if (dirEntry.getName() != null && !"".equals(dirEntry.getName())) { entries.put(dirEntry.getName(), dirEntry); } } }); } return entries; } }; final SVNReporter17 reporter = new SVNReporter17(localAbsPath, getWcContext(), true, !serverSupportsDepth, depth, getOperation().isUpdateLocksOnDemand(), false, !depthIsSticky, useCommitTimes, null); final ISVNUpdateEditor editor = SVNUpdateEditor17.createUpdateEditor(getWcContext(), revnum, anchor, target, wcIprops, useCommitTimes, switchRevUrl, depth, depthIsSticky, allowUnversionedObstructions, false, serverSupportsDepth, false, dirFetcher, externalsStore, preservedExts, getOperation().getOptions().getConflictResolver()); try { //update() method in SVNKit doesn't allow to use ignoreAncestry=false, so we use diff() method repository.diff(switchRevUrl, revnum, revnum, target, ignoreAncestry, depthIsSticky ? depth : SVNDepth.UNKNOWN, true, reporter, editor); } catch (SVNException e) { sleepForTimestamp(); throw e; } if (depth.isRecursive() && !getOperation().isIgnoreExternals()) { getWcContext().getDb().gatherExternalDefinitions(localAbsPath, externalsStore); handleExternals(externalsStore.getNewExternals(), externalsStore.getDepths(), anchorUrl, localAbsPath, switchRootUrl, depth, true); } if (sleepForTimestamp) { sleepForTimestamp(); } handleEvent(SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.NONE, null, revnum, SVNEventAction.UPDATE_COMPLETED, null, null, null, reporter.getReportedFilesCount(), reporter.getTotalFilesCount())); return editor.getTargetRevision(); } protected long checkout(SVNURL url, File localAbspath, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, boolean ignoreExternals, boolean allowUnversionedObstructions, boolean sleepForTimestamp, int targetWorkingCopyFormat) throws SVNException { Structure<RepositoryInfo> repositoryInfo = getRepositoryAccess().createRepositoryFor( SvnTarget.fromURL(url), revision, pegRevision, null); url = repositoryInfo.<SVNURL>get(RepositoryInfo.url); SVNRepository repository = repositoryInfo.<SVNRepository>get(RepositoryInfo.repository); long revnum = repositoryInfo.lng(RepositoryInfo.revision); repositoryInfo.release(); SVNURL rootUrl = repository.getRepositoryRoot(true); String uuid = repository.getRepositoryUUID(true); SVNNodeKind kind = repository.checkPath("", revnum); if (kind == 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 (kind == SVNNodeKind.NONE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' doesn''t exist", url); SVNErrorManager.error(err, SVNLogType.WC); } SVNFileType fileKind = SVNFileType.getType(localAbspath); if (fileKind == SVNFileType.NONE) { SVNFileUtil.ensureDirectoryExists(localAbspath); getWcContext().initializeWC(localAbspath, url, rootUrl, uuid, revnum, depth == SVNDepth.UNKNOWN ? SVNDepth.INFINITY : depth, targetWorkingCopyFormat); } else if (fileKind == SVNFileType.DIRECTORY) { int formatVersion = getWcContext().checkWC(localAbspath); if (formatVersion >= SVNWCDb.WC_FORMAT_17 && SvnOperationFactory.isVersionedDirectory(localAbspath)) { SVNURL entryUrl = getWcContext().getNodeUrl(localAbspath); if (entryUrl != null && !url.equals(entryUrl)) { String message = "''{0}'' is already a working copy for a different URL"; message += "; perform update to complete it"; SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, message, localAbspath); SVNErrorManager.error(err, SVNLogType.WC); } } else { depth = depth == SVNDepth.UNKNOWN ? SVNDepth.INFINITY : depth; getWcContext().initializeWC(localAbspath, url, rootUrl, uuid, revnum, depth == SVNDepth.UNKNOWN ? SVNDepth.INFINITY : depth, targetWorkingCopyFormat); } } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NODE_KIND_CHANGE, "''{0}'' already exists and is not a directory", localAbspath); SVNErrorManager.error(err, SVNLogType.WC); } return update(getWcContext(), localAbspath, revision, depth, true, ignoreExternals, allowUnversionedObstructions, true, false, false, sleepForTimestamp); } protected static boolean isEmptyWc(File root, File anchorAbspath) { if (!root.equals(anchorAbspath)) { return false; } File[] children = SVNFileListUtil.listFiles(root); if (children != null) { return children.length == 1 && SVNFileUtil.getAdminDirectoryName().equals(children[0].getName()); } return true; } private static class RecordConflictsResolver implements ISVNConflictHandler { private final List<SVNConflictDescription> conflicts; private RecordConflictsResolver() { this.conflicts = new ArrayList<SVNConflictDescription>(); } public SVNConflictResult handleConflict(SVNConflictDescription conflictDescription) throws SVNException { conflicts.add(conflictDescription); return new SVNConflictResult(SVNConflictChoice.POSTPONE, null); } private List<SVNConflictDescription> getConflicts() { return conflicts; } public boolean hasConflicts() { return conflicts.size() > 0; } } }