package org.tmatesoft.svn.core.internal.wc17.db; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb; import org.tmatesoft.svn.core.internal.db.SVNSqlJetUpdateStatement; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc17.SVNWCContext; import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbKind; import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbStatus; import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDb.DirParsedInfo; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.AdditionInfo; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.DeletionInfo; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeInfo; import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.RepositoryInfo; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema.LOCK__Fields; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema.NODES__Fields; import org.tmatesoft.svn.util.SVNLogType; import java.io.File; import java.util.HashMap; import java.util.Map; public class SvnWcDbRelocate extends SvnWcDbShared { public static interface ISvnRelocateValidator { void validateRelocation(String uuid, SVNURL newUrl, SVNURL newRepositoryRoot) throws SVNException; } public static void relocate(SVNWCContext context, File localAbspath, SVNURL from, SVNURL to, ISvnRelocateValidator validator) throws SVNException { if (!context.getDb().isWCRoot(localAbspath)) { try { File wcRootAbsPath = context.getDb().getWCRoot(localAbspath); SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_OP_ON_CWD, "Cannot relocate ''{0}'' as it is not the root of a working copy; try relocating ''{1}'' instead", localAbspath, wcRootAbsPath); SVNErrorManager.error(errorMessage, SVNLogType.WC); } catch (SVNException e) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_OP_ON_CWD, "Cannot relocate ''{0}'' as it is not the root of a working copy", localAbspath); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } Structure<NodeInfo> nodeInfoStructure = context.getDb().readInfo(localAbspath, NodeInfo.kind, NodeInfo.reposRelPath, NodeInfo.reposRootUrl, NodeInfo.reposUuid); SVNWCDbKind kind = nodeInfoStructure.get(NodeInfo.kind); File reposRelPath = nodeInfoStructure.get(NodeInfo.reposRelPath); SVNURL oldReposRoot = nodeInfoStructure.get(NodeInfo.reposRootUrl); String uuid = nodeInfoStructure.get(NodeInfo.reposUuid); if (kind != SVNWCDbKind.Dir) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_INVALID_RELOCATION, "Cannot relocate a single file"); SVNErrorManager.error(errorMessage, SVNLogType.WC); } SVNURL oldUrl = oldReposRoot.appendPath(SVNFileUtil.getFilePath(reposRelPath), false); if (!oldUrl.toString().startsWith(from.toString())) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_RELOCATION, "Invalid source URL prefix: ''{0}'' (does not overlap target's URL ''{1}'')", from, oldUrl); SVNErrorManager.error(errorMessage, SVNLogType.WC); } SVNURL newUrl; if (oldUrl.equals(from)) { newUrl = to; } else { newUrl = SVNURL.parseURIEncoded(to.toString() + oldUrl.toString().substring(from.toString().length()));; } String relPathStr = SVNFileUtil.getFilePath(reposRelPath); String newReposRootPath = newUrl.getPath(); if (!newReposRootPath.endsWith(relPathStr)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_RELOCATION, "Invalid relocation destination: ''{0}'' (does not " + "point to target)", newUrl); SVNErrorManager.error(err, SVNLogType.WC); } newReposRootPath = newReposRootPath.substring(0, newReposRootPath.length() - relPathStr.length()); SVNURL newReposRoot = newUrl.setPath(newReposRootPath, false); if (validator != null) { validator.validateRelocation(uuid, newUrl, newReposRoot); } relocate((SVNWCDb) context.getDb(), localAbspath, newReposRoot); } private static void relocate(SVNWCDb db, File localAbspath, SVNURL repositoryRootUrl) throws SVNException { DirParsedInfo dirInfo = db.obtainWcRoot(localAbspath); File localRelpath = dirInfo.localRelPath; SVNWCDbRoot root = dirInfo.wcDbDir.getWCRoot(); Structure<NodeInfo> nodeInfo = SvnWcDbShared.readInfo(root, localRelpath, NodeInfo.status, NodeInfo.reposId, NodeInfo.haveBase); SVNWCDbStatus status = nodeInfo.<SVNWCDbStatus>get(NodeInfo.status); long oldReposId = nodeInfo.lng(NodeInfo.reposId); boolean haveBase = nodeInfo.is(NodeInfo.haveBase); nodeInfo.release(); if (status == SVNWCDbStatus.Excluded) { File parentRelPath = SVNFileUtil.getFileDir(localRelpath); nodeInfo = SvnWcDbShared.readInfo(root, localRelpath, NodeInfo.status, NodeInfo.reposId); status = nodeInfo.<SVNWCDbStatus>get(NodeInfo.status); oldReposId = nodeInfo.lng(NodeInfo.reposId); localRelpath = parentRelPath; nodeInfo.release(); } if (oldReposId == SVNWCContext.INVALID_REVNUM) { if (status == SVNWCDbStatus.Deleted) { Structure<DeletionInfo> deletionInfo = scanDeletion(root, localRelpath); if (deletionInfo.hasValue(DeletionInfo.workDelRelPath)) { status = SVNWCDbStatus.Added; localRelpath = SVNFileUtil.getFileDir(deletionInfo.<File>get(DeletionInfo.workDelRelPath)); } deletionInfo.release(); } if (status == SVNWCDbStatus.Added) { Structure<AdditionInfo> additionInfo = scanAddition(root, localRelpath, AdditionInfo.reposId); oldReposId = additionInfo.lng(AdditionInfo.reposId); additionInfo.release(); } else { Structure<NodeInfo> baseInfo = getDepthInfo(root, localRelpath, 0, NodeInfo.reposId); oldReposId = baseInfo.lng(NodeInfo.reposId); baseInfo.release(); } } Structure<RepositoryInfo> repositoryInfo = db.fetchRepositoryInfo(root.getSDb(), oldReposId); String reposUuid = repositoryInfo.text(RepositoryInfo.reposUuid); repositoryInfo.release(); begingWriteTransaction(root); try { relocate(root, localRelpath, repositoryRootUrl, reposUuid, haveBase, oldReposId); } catch(SVNException e) { rollbackTransaction(root); throw e; } finally { commitTransaction(root); } } private static void relocate(SVNWCDbRoot root, File localRelPath, SVNURL reposRootUrl, String reposUuid, boolean haveBaseNode, long oldReposId) throws SVNException { long newReposId = root.getDb().createReposId(root.getSDb(), reposRootUrl, reposUuid); SVNSqlJetUpdateStatement stmt = new RecursiveUpdateNodeRepo(root.getSDb()); try { stmt.bindf("isii", root.getWcId(), localRelPath, oldReposId, newReposId); stmt.done(); } finally { stmt.reset(); } if (haveBaseNode) { stmt = new UpdateLockReposId(root.getSDb()); try { stmt.bindf("ii", oldReposId, newReposId); stmt.done(); } finally { stmt.reset(); } } } /** * UPDATE lock SET repos_id = ?2 * WHERE repos_id = ?1 */ private static class UpdateLockReposId extends SVNSqlJetUpdateStatement { private Map<String, Object> updateValues; public UpdateLockReposId(SVNSqlJetDb sDb) throws SVNException { super(sDb, SVNWCDbSchema.LOCK); } @Override public Map<String, Object> getUpdateValues() throws SVNException { if (updateValues == null) { updateValues = new HashMap<String, Object>(); } else { updateValues.clear(); } updateValues.put(LOCK__Fields.repos_id.toString(), getBind(2)); return updateValues; } @Override protected Object[] getWhere() throws SVNException { return new Object[0]; } @Override protected boolean isFilterPassed() throws SVNException { long queryReposId = (Long) getBind(1); return getColumnLong(LOCK__Fields.repos_id) == queryReposId; } } /** * UPDATE nodes SET repos_id = ?4, dav_cache = NULL * WHERE wc_id = ?1 * AND repos_id = ?3 * AND (?2 = '' * OR local_relpath = ?2 * OR (local_relpath > ?2 || '/' AND local_relpath < ?2 || '0')) * */ private static class RecursiveUpdateNodeRepo extends SVNSqlJetUpdateStatement { private Map<String, Object> updateValues; public RecursiveUpdateNodeRepo(SVNSqlJetDb sDb) throws SVNException { super(sDb, SVNWCDbSchema.NODES); } @Override public Map<String, Object> getUpdateValues() throws SVNException { if (updateValues == null) { updateValues = new HashMap<String, Object>(); } else { updateValues.clear(); } updateValues.put(NODES__Fields.repos_id.toString(), getBind(4)); updateValues.put(NODES__Fields.dav_cache.toString(), null); updateValues.put(NODES__Fields.properties.toString(), getColumnBlob(NODES__Fields.properties)); return updateValues; } @Override protected Object[] getWhere() throws SVNException { return new Object[] {getBind(1)}; } @Override protected boolean isFilterPassed() throws SVNException { if (super.isFilterPassed()) { long queryReposId = (Long) getBind(3); if (getColumnLong(NODES__Fields.repos_id) == queryReposId) { String queryPath = (String) getBind(2); if ("".equals(queryPath)) { return true; } String rowPath = getColumnString(NODES__Fields.local_relpath); return rowPath.equals(queryPath) || rowPath.startsWith(queryPath + "/"); } } return false; } } }