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;
}
}
}