package org.tmatesoft.svn.core.internal.wc2.old;
import org.tmatesoft.sqljet.core.SqlJetTransactionMode;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetStatement;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.util.SVNSkel;
import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
import org.tmatesoft.svn.core.internal.wc.*;
import org.tmatesoft.svn.core.internal.wc.admin.*;
import org.tmatesoft.svn.core.internal.wc16.SVNStatusClient16;
import org.tmatesoft.svn.core.internal.wc16.SVNUpdateClient16;
import org.tmatesoft.svn.core.internal.wc16.SVNWCClient16;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils;
import org.tmatesoft.svn.core.internal.wc17.db.*;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbOpenMode;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbUpgradeData;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbStatements;
import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.internal.wc2.old.SvnOldUpgradeEntries.WriteBaton;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.SVNEvent;
import org.tmatesoft.svn.core.wc.SVNEventAction;
import org.tmatesoft.svn.core.wc2.*;
import org.tmatesoft.svn.core.wc2.SvnChecksum.Kind;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
import java.io.File;
import java.io.InputStream;
import java.util.*;
public class SvnOldUpgrade extends SvnOldRunner<SvnWcGeneration, SvnUpgrade> {
/* WC-1.0 administrative area extensions */
private static final String SVN_WC__BASE_EXT = ".svn-base"; /* for text and prop bases */
private static final String SVN_WC__REVERT_EXT = ".svn-revert"; /* for reverting a replaced file */
/* Old locations for storing "wcprops" (aka "dav cache"). */
private static final String WCPROPS_SUBDIR_FOR_FILES = "wcprops";
private static final String WCPROPS_FNAME_FOR_DIR = "dir-wcprops";
private static final String WCPROPS_ALL_DATA = "all-wcprops";
/* Old property locations. */
private static final String PROPS_SUBDIR = "props";
private static final String PROP_BASE_SUBDIR = "prop-base";
private static final String PROP_BASE_FOR_DIR = "dir-prop-base";
private static final String PROP_REVERT_FOR_DIR = "dir-prop-revert";
private static final String PROP_WORKING_FOR_DIR = "dir-props";
/* Old textbase location. */
private static final String TEXT_BASE_SUBDIR = "text-base";
/* Old data files that we no longer need/use. */
private static final String ADM_README = "README.txt";
private static final String ADM_EMPTY_FILE = "empty-file";
private static final String ADM_LOG = "log";
private static final String ADM_LOCK = "lock";
/* New pristine location */
private static final String PRISTINE_STORAGE_RELPATH = "pristine";
private static final String SDB_FILE = "wc.db";
public static SVNURL getEntryRepositoryRootURL(SVNEntry entry) throws SVNException {
return getCanonicalURL(entry.getRepositoryRootURL());
}
public static SVNURL getEntryURL(SVNEntry entry) throws SVNException {
return getCanonicalURL(entry.getSVNURL());
}
public static SVNURL getEntryCopyFromURL(SVNEntry entry) throws SVNException {
return getCanonicalURL(entry.getCopyFromSVNURL());
}
private static SVNURL getCanonicalURL(SVNURL original) throws SVNException {
if (original == null) {
return null;
}
SVNURL canonical = SVNUpdateClient16.canonicalizeURL(original, true);
return canonical != null ? canonical : original;
}
private class RepositoryInfo {
public SVNURL repositoryRootUrl = null;
public String UUID = null;
}
private SVNWCAccess access = null;
private SVNWCAccess getWCAccess() {
if (access == null) {
access = SVNWCAccess.newInstance(getOperation().getEventHandler());
access.setOptions(getOperation().getOptions());
}
return access;
}
@Override
protected SvnWcGeneration run() throws SVNException {
final int targetWorkingCopyFormat = getOperation().getTargetWorkingCopyFormat();
final int currentWorkingCopyFormat = readWorkingCopyFormat(getFirstTarget());
if (targetWorkingCopyFormat == currentWorkingCopyFormat) {
//do nothing
return SvnWcGeneration.V16;
} else if (targetWorkingCopyFormat < currentWorkingCopyFormat) {
//1.6->1.5, 1.6.->1.4, 1.5->1.4
SVNWCClient16 client = new SVNWCClient16(getOperation().getRepositoryPool(), getOperation().getOptions());
client.setEventHandler(getOperation().getEventHandler());
client.setDebugLog(SVNDebugLog.getDefaultLog());
client.doSetWCFormat(getFirstTarget(), targetWorkingCopyFormat);
return SvnWcGeneration.V16;
} else if (targetWorkingCopyFormat >= SVNAdminArea16.WC_FORMAT) {
//1.5->1.7, 1.4->1.8, 1.6->1.7, 1.4->1.6 and so on
//then upgrade to 1.6
if (currentWorkingCopyFormat < SVNAdminArea16.WC_FORMAT) {
SVNWCClient16 client = new SVNWCClient16(getOperation().getRepositoryPool(), getOperation().getOptions());
client.setEventHandler(getOperation().getEventHandler());
client.setDebugLog(SVNDebugLog.getDefaultLog());
client.doSetWCFormat(getFirstTarget(), SVNAdminArea16.WC_FORMAT);
}
//and continue upgrading only if targetWorkingCopyFormat > 1.6
if (targetWorkingCopyFormat == SVNAdminArea16.WC_FORMAT) {
return SvnWcGeneration.V16;
}
}
if (getOperation().getFirstTarget().isURL()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "'{0}' is not a local path", getOperation().getFirstTarget().getURL());
SVNErrorManager.error(err, SVNLogType.WC);
}
File localAbsPath = getOperation().getFirstTarget().getFile().getAbsoluteFile();
RepositoryInfo reposInfo = new RepositoryInfo();
wcUpgrade(localAbsPath, reposInfo);
/*
* Now it's time to upgrade the externals too. We do it after the wc upgrade to avoid that errors in the externals causes the wc upgrade
* to fail. Thanks to caching the performance penalty of walking the wc a second time shouldn't be too severe
*/
final Map<SvnTarget, String> externals = new HashMap<SvnTarget, String>();
SvnGetProperties getProperties = getOperation().getOperationFactory().createGetProperties();
getProperties.addTarget(getOperation().getFirstTarget());
getProperties.setDepth(SVNDepth.INFINITY);
getProperties.setReceiver(new ISvnObjectReceiver<SVNProperties>() {
public void receive(SvnTarget target, SVNProperties object)
throws SVNException {
String value = object.getStringValue(SVNProperty.EXTERNALS);
if (value != null) {
externals.put(target, value);
}
}
});
getProperties.run();
for (SvnTarget target : externals.keySet()) {
SVNExternal[] externalDefs = SVNExternal.parseExternals(target.getFile(), externals.get(target));
for (int i = 0; i < externalDefs.length; i++) {
File externalPath = SVNFileUtil.createFilePath(target.getFile(), externalDefs[i].getPath());
try {
getWcContext().readKind(externalPath.getAbsoluteFile(), false);
} catch (SVNException e) {
if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_UNSUPPORTED_FORMAT) {
getProperties.getOperationFactory().getWcContext().getDb().close();
SvnUpgrade upgradeExternal = getOperation().getOperationFactory().createUpgrade();
upgradeExternal.setSingleTarget(SvnTarget.fromFile(externalPath));
upgradeExternal.run();
} else {
throw e;
}
}
}
}
return SvnWcGeneration.V17;
}
private int readWorkingCopyFormat(File firstTarget) throws SVNException {
SVNWCAccess wcAccess = getWCAccess();
SVNAdminArea adminArea = wcAccess.probeOpen(firstTarget, false, 0);
final int currentWorkingCopyFormat = adminArea.getFormatVersion();
wcAccess.close();
return currentWorkingCopyFormat;
}
private void checkIsOldWCRoot(File localAbsPath) throws SVNException {
SVNWCAccess wcAccess = getWCAccess();
try {
readEntries(wcAccess, localAbsPath);
} catch (SVNException e) {
SVNErrorMessage err = SVNErrorMessage.create(
SVNErrorCode.WC_INVALID_OP_ON_CWD, "Can''t upgrade ''{0}'' as it is not a working copy directory", localAbsPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
if (SVNFileUtil.getParentFile(localAbsPath) == null) {
return;
}
File parentAbsPath = SVNFileUtil.getParentFile(localAbsPath);
SVNEntry entry = null;
Map<String, SVNEntry> entries = null;
try {
entries = readEntries(wcAccess, parentAbsPath);
} catch (SVNException ex) {
return;
}
entry = entries.get(localAbsPath.getName());
if (entry == null
|| entry.isAbsent()
|| (entry.isDeleted() && !entry.isScheduledForAddition())
|| entry.getDepth() == SVNDepth.EXCLUDE) {
return;
}
File childAbsPath;
while (SVNFileUtil.getParentFile(parentAbsPath) != null) {
childAbsPath = parentAbsPath;
parentAbsPath = SVNFileUtil.getParentFile(parentAbsPath);
try {
entries = readEntries(wcAccess, parentAbsPath);
} catch (SVNException e) {
parentAbsPath = childAbsPath;
break;
}
entry = entries.get(localAbsPath.getName());
if (entry == null
|| entry.isAbsent()
|| (entry.isDeleted() && !entry.isScheduledForAddition())
|| entry.getDepth() == SVNDepth.EXCLUDE) {
parentAbsPath = childAbsPath;
break;
}
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_INVALID_OP_ON_CWD,
"Can''t upgrade ''{0}'' as it is not a working copy root, the root is ''{1}''", localAbsPath, parentAbsPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
private void fetchReposInfo(SVNEntry entry, RepositoryInfo lastRepositoryInfo) throws SVNException {
/* The same info is likely to retrieved multiple times (e.g. externals) */
if (lastRepositoryInfo.repositoryRootUrl != null && SVNURLUtil.isAncestor(lastRepositoryInfo.repositoryRootUrl, getEntryURL(entry))) {
entry.setRepositoryRootURL(lastRepositoryInfo.repositoryRootUrl);
entry.setUUID(lastRepositoryInfo.UUID);
return;
}
SvnRepositoryAccess repAccess = new SvnOldRepositoryAccess(getOperation());
SVNRepository repository = repAccess.createRepository(getEntryURL(entry), null, true);
entry.setRepositoryRootURL(repository.getRepositoryRoot(true));
entry.setUUID(repository.getRepositoryUUID(true));
lastRepositoryInfo.repositoryRootUrl = getEntryRepositoryRootURL(entry);
lastRepositoryInfo.UUID = entry.getUUID();
}
private void ensureReposInfo(SVNEntry entry, File localAbsPath, RepositoryInfo lastRepositoryInfo, Map<SVNURL, String> reposCache) throws SVNException {
if (getEntryRepositoryRootURL(entry) != null && entry.getUUID() != null) {
return;
}
if ((getEntryRepositoryRootURL(entry) == null || entry.getUUID() == null) && getEntryURL(entry) != null) {
for (Iterator<SVNURL> items = reposCache.keySet().iterator(); items.hasNext();) {
SVNURL reposRootUrl = items.next();
if (SVNURLUtil.isAncestor(reposRootUrl, getEntryURL(entry))) {
if (getEntryRepositoryRootURL(entry) == null) {
entry.setRepositoryRootURL(reposRootUrl);
}
if (entry.getUUID() == null) {
entry.setUUID((String) reposCache.get(reposRootUrl));
}
return;
}
}
}
if (getEntryURL(entry) == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_UNSUPPORTED_FORMAT,
"Working copy ''{0}'' can't be upgraded because it doesn't have a url", localAbsPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
fetchReposInfo(entry, lastRepositoryInfo);
}
private void wcUpgrade(File localAbsPath, RepositoryInfo reposInfo) throws SVNException {
SVNWCDbUpgradeData upgradeData = new SVNWCDbUpgradeData();
checkIsOldWCRoot(localAbsPath);
/*
* Given a pre-wcng root some/wc we create a temporary wcng in some/wc/.svn/tmp/wcng/wc.db and copy the metadata from one to the
* other, then the temporary wc.db file gets moved into the original root. Until the wc.db file is moved the original working copy remains
* a pre-wcng and 'cleanup' with an old client will remove the partial upgrade. Moving the wc.db file creates a wcng, and 'cleanup' with a
* new client will complete any outstanding upgrade.
*/
SVNWCDb db = new SVNWCDb();
db.open(SVNWCDbOpenMode.ReadWrite, (ISVNOptions) null, false, false);
SVNWCContext wcContext = new SVNWCContext(db, getOperation().getEventHandler());
SVNWCAccess wcAccess = getWCAccess();
SVNEntry thisDir = null;
try {
wcAccess.probeOpen(localAbsPath, false, 0);
thisDir = wcAccess.getEntry(localAbsPath, false);
} finally {
wcAccess.close();
}
Map<SVNURL, String> reposCache = new HashMap<SVNURL, String>();
ensureReposInfo(thisDir, localAbsPath, reposInfo, reposCache);
/*
* Cache repos UUID pairs for when a subdir doesn't have this information
*/
if (!reposCache.containsKey(getEntryRepositoryRootURL(thisDir)))
reposCache.put(getEntryRepositoryRootURL(thisDir), thisDir.getUUID());
/* Create the new DB in the temporary root wc/.svn/tmp/wcng/.svn */
upgradeData.rootAbsPath = SVNFileUtil.createFilePath(SVNWCUtils.admChild(localAbsPath, "tmp"), "wcng");
File rootAdmAbsPath = SVNWCUtils.admChild(upgradeData.rootAbsPath, "");
try {
SVNFileUtil.deleteAll(rootAdmAbsPath, true);
SVNFileUtil.ensureDirectoryExists(rootAdmAbsPath);
/*
* Create an empty sqlite database for this directory and store it in DB.
*/
db.upgradeBegin(upgradeData.rootAbsPath, upgradeData, getEntryRepositoryRootURL(thisDir), thisDir.getUUID(), getOperation().getTargetWorkingCopyFormat());
/*
* Migrate the entries over to the new database. ### We need to think about atomicity here.
*
* entries_write_new() writes in current format rather than f12.
* Thus, this function bumps a working copy all the way to current.
*/
db.obtainWCLock(upgradeData.rootAbsPath, 0, false);
upgradeData.root.getSDb().beginTransaction(SqlJetTransactionMode.WRITE);
try {
upgradeWorkingCopy(null, db, localAbsPath, upgradeData, reposCache, reposInfo);
} catch (SVNException ex) {
upgradeData.root.getSDb().rollback();
throw ex;
} finally {
upgradeData.root.getSDb().commit();
}
/* A workqueue item to move the pristine dir into place */
File pristineFrom = SVNWCUtils.admChild(upgradeData.rootAbsPath, PRISTINE_STORAGE_RELPATH);
File pristineTo = SVNWCUtils.admChild(localAbsPath, PRISTINE_STORAGE_RELPATH);
SVNFileUtil.ensureDirectoryExists(pristineFrom);
SVNSkel workItems = null;
SVNSkel workItem = wcContext.wqBuildFileMove(localAbsPath, pristineFrom, pristineTo);
workItems = wcContext.wqMerge(workItems, workItem);
/* A workqueue item to remove pre-wcng metadata */
workItem = wcContext.wqBuildPostUpgrade();
workItems = wcContext.wqMerge(workItems, workItem);
db.addWorkQueue(upgradeData.rootAbsPath, workItems);
db.releaseWCLock(upgradeData.rootAbsPath);
db.close();
/* Renaming the db file is what makes the pre-wcng into a wcng */
File dbFrom = SVNWCUtils.admChild(upgradeData.rootAbsPath, SDB_FILE);
File dbTo = SVNWCUtils.admChild(localAbsPath, SDB_FILE);
SVNFileUtil.rename(dbFrom, dbTo);
db.open(SVNWCDbOpenMode.ReadWrite, (ISVNOptions) null, false, false);
wcContext = new SVNWCContext(db, getOperation().getEventHandler());
wcContext.wqRun(localAbsPath);
} finally {
db.close();
SVNFileUtil.deleteAll(upgradeData.rootAbsPath, true);
}
if (getOperation().getEventHandler() != null) {
SVNEvent event = SVNEventFactory.createSVNEvent(localAbsPath, SVNNodeKind.DIR, null, -1, SVNEventAction.UPGRADE, null, null, null);
getOperation().getEventHandler().handleEvent(event, -1);
}
}
private void upgradeWorkingCopy(WriteBaton parentDirBaton, SVNWCDb db, File dirAbsPath, SVNWCDbUpgradeData data, Map<SVNURL, String> reposCache, RepositoryInfo reposInfo) throws SVNException {
WriteBaton dirBaton = null;
if (getOperation().getEventHandler() != null)
getOperation().getEventHandler().checkCancelled();
int oldFormat = db.getFormatTemp(dirAbsPath);
if (oldFormat >= SVNWCContext.WC_NG_VERSION) {
if (getOperation().getEventHandler() != null) {
SVNEvent event = SVNEventFactory.createSVNEvent(dirAbsPath,SVNNodeKind.DIR, null, -1, SVNEventAction.SKIP, SVNEventAction.UPGRADED_PATH, null, null);
getOperation().getEventHandler().handleEvent(event, -1);
}
return;
}
ArrayList<File> children = new ArrayList<File>();
try {
getVersionedSubdirs(getWCAccess(), dirAbsPath, children, false, false);
} catch (SVNException ex) {
if (ex.isEnoent()) {
if (getOperation().getEventHandler() != null) {
SVNEvent event = SVNEventFactory.createSVNEvent(dirAbsPath, SVNNodeKind.DIR, null, -1, SVNEventAction.SKIP, SVNEventAction.UPGRADED_PATH, null, null);
getOperation().getEventHandler().handleEvent(event, -1);
}
}
return;
}
dirBaton = upgradeToWcng(parentDirBaton, db, dirAbsPath, oldFormat, data, reposCache, reposInfo);
if (getOperation().getEventHandler() != null) {
SVNEvent event = SVNEventFactory.createSVNEvent(dirAbsPath, SVNNodeKind.DIR, null, -1, SVNEventAction.UPGRADED_PATH, null, null, null);
getOperation().getEventHandler().handleEvent(event, -1);
}
for (File childAbsPath : children) {
upgradeWorkingCopy(dirBaton, db, childAbsPath, data, reposCache, reposInfo);
}
}
@SuppressWarnings("unchecked")
private WriteBaton upgradeToWcng(WriteBaton parentDirBaton, SVNWCDb db, File dirAbsPath, int oldFormat, SVNWCDbUpgradeData data, Map<SVNURL, String> reposCache, RepositoryInfo reposInfo) throws SVNException {
WriteBaton dirBaton = null;
File logFilePath = SVNWCUtils.admChild(dirAbsPath, ADM_LOG);
/* Don't try to mess with the WC if there are old log files left. */
/* Is the (first) log file present? */
SVNNodeKind logFileKind = SVNFileType.getNodeKind(SVNFileType.getType(logFilePath));
if (logFileKind == SVNNodeKind.FILE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_UNSUPPORTED_FORMAT,
"Cannot upgrade with existing logs; run a cleanup operation on this working copy using "
+ "a client version which is compatible with this working copy's format (such as the version "
+ "you are upgrading from), then retry the upgrade with the current version");
SVNErrorManager.error(err, SVNLogType.WC);
}
/*
* Lock this working copy directory, or steal an existing lock. Do this
* BEFORE we read the entries. We don't want another process to modify the entries after we've read them into memory.
*/
createPhysicalLock(dirAbsPath);
/*
* What's going on here?
*
* We're attempting to upgrade an older working copy to the new wc-ng format. The semantics and storage mechanisms between the two are
* vastly different, so it's going to be a bit painful. Here's a plan for the operation:
*
* 1) Read the old 'entries' using the old-format reader.
*
* 2) Create the new DB if it hasn't already been created.
*
* 3) Use our compatibility code for writing entries to fill out the (new) DB state. Use the remembered checksums, since an entry has only
* the MD5 not the SHA1 checksum, and in the case of a revert-base doesn't even have that.
*
* 4) Convert wcprop to the wc-ng format
*
* 5) Migrate regular properties to the WC-NG DB.
*/
/***** ENTRIES - READ *****/
SVNWCAccess access = getWCAccess();
Map<String, SVNEntry> entries = null;
try {
SVNAdminArea area = access.probeOpen(dirAbsPath, false, 0);
entries = area.getEntries();
SVNEntry thisDir = entries.get("");
ensureReposInfo(thisDir, dirAbsPath, reposInfo, reposCache);
/*
* Cache repos UUID pairs for when a subdir doesn't have this information
*/
if (!reposCache.containsKey(getEntryRepositoryRootURL(thisDir))) {
reposCache.put(getEntryRepositoryRootURL(thisDir), thisDir.getUUID());
}
String dirAbsPathString = SVNFileUtil.getFilePath(dirAbsPath);
String oldWcRootAbsPath = SVNPathUtil.getCommonPathAncestor(dirAbsPathString, SVNFileUtil.getFilePath(data.rootAbsPath));
File dirRelPath = new File(SVNPathUtil.getRelativePath(oldWcRootAbsPath, dirAbsPathString));
/***** TEXT BASES *****/
SVNHashMap textBases = migrateTextBases(dirAbsPath, data.rootAbsPath, data.root);
/***** ENTRIES - WRITE *****/
try {
dirBaton = SvnOldUpgradeEntries.writeUpgradedEntries(parentDirBaton, db, data, dirAbsPath, entries, textBases, getOperation().getTargetWorkingCopyFormat());
} catch (SVNException ex) {
if (ex.getErrorMessage().getErrorCode() == SVNErrorCode.WC_CORRUPT) {
SVNErrorMessage err = ex.getErrorMessage().wrap("This working copy is corrupt and cannot be upgraded. Please check out a new working copy.");
SVNErrorManager.error(err, SVNLogType.WC);
}
if (ex.getErrorMessage().getErrorCode() == SVNErrorCode.WC_INVALID_SCHEDULE) {
throw ex;
}
}
/***** WC PROPS *****/
/* If we don't know precisely where the wcprops are, ignore them. */
if (oldFormat != SVNWCContext.WC_WCPROPS_LOST) {
/*
* if (oldFormat <= SVNWCContext.WC_WCPROPS_MANY_FILES_VERSION)
* allProps = readManyWcProps(dirAbsPath); else allProps = readWcProps(dirAbsPath);
*/
SVNHashMap cachedProps = new SVNHashMap();
SVNVersionedProperties verProps = area.getWCProperties("");
if (verProps != null) {
cachedProps.put("", verProps.asMap());
}
Set<File> children = getVersionedFiles(dirRelPath, data.root.getSDb(), data.workingCopyId);
for (Iterator<File> files = children.iterator(); files.hasNext();) {
File file = files.next();
verProps = area.getWCProperties(SVNFileUtil.getFileName(file));
if (verProps != null) {
cachedProps.put(SVNFileUtil.getFileName(file), verProps.asMap());
}
}
SvnWcDbProperties.upgradeApplyDavCache(data.root, dirRelPath, cachedProps);
}
/*
* Upgrade all the properties (including "this dir"). Note: this must come AFTER the entries have been migrated into the database.
* The upgrade process needs the children in BASE_NODE and WORKING_NODE, and to examine the resultant WORKING state.
*/
migrateProps(dirAbsPath, data, oldFormat, area);
} finally {
access.close();
}
return dirBaton;
}
/*
* The checksums of one pre-1.7 text-base file. If the text-base file
* exists, both checksums are filled in, otherwise both fields are NULL.
*/
public class TextBaseFileInfo {
public SvnChecksum sha1Checksum;
public SvnChecksum md5Checksum;
}
/*
* The text-base checksums of the normal base and/or the revert-base of one
* pre-1.7 versioned text file.
*/
public class TextBaseInfo {
public TextBaseFileInfo normalBase = new TextBaseFileInfo();
public TextBaseFileInfo revertBase = new TextBaseFileInfo();
}
/*
* Copy all the text-base files from the administrative area of WC directory
* dirAbsPath into the pristine store of SDB which is located in directory
* newWcRootAbsPath. Returns SVNHashMap that maps name of the versioned file
* to (svn_wc__text_base_info_t *) information about the pristine text.
*/
private SVNHashMap migrateTextBases(File dirAbsPath, File newWcRootAbsPath, SVNWCDbRoot root) throws SVNException {
SVNHashMap textBasesInfo = new SVNHashMap();
File textBaseDir = SVNWCUtils.admChild(dirAbsPath, TEXT_BASE_SUBDIR);
File[] files = SVNFileListUtil.listFiles(textBaseDir);
if (files == null)
return textBasesInfo;
for (File textBasePath : files) {
SvnChecksum md5Checksum = null;
SvnChecksum sha1Checksum = null;
File pristinePath;
/* Calculate its checksums and copy it to the pristine store */
{
File tempPath = SVNFileUtil.createUniqueFile(newWcRootAbsPath, "upgrade", ".tmp", false);
InputStream readStream = SVNFileUtil.openFileForReading(textBasePath);
SVNChecksumInputStream readChecksummedIS = null;
try {
readChecksummedIS = new SVNChecksumInputStream(readStream, "SHA1");
} finally {
SVNFileUtil.closeFile(readChecksummedIS);
SVNFileUtil.closeFile(readStream);
}
sha1Checksum = readChecksummedIS != null ? new SvnChecksum(Kind.sha1, readChecksummedIS.getDigest()) : null;
readStream = SVNFileUtil.openFileForReading(textBasePath);
try {
readChecksummedIS = new SVNChecksumInputStream(readStream, SVNChecksumInputStream.MD5_ALGORITHM);
} finally {
SVNFileUtil.closeFile(readChecksummedIS);
SVNFileUtil.closeFile(readStream);
}
md5Checksum = readChecksummedIS != null ? new SvnChecksum(Kind.md5, readChecksummedIS.getDigest()) : null;
SVNFileUtil.copyFile(textBasePath, tempPath, true);
/* Insert a row into the pristine table. */
SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.INSERT_OR_IGNORE_PRISTINE);
try {
stmt.bindChecksum(1, sha1Checksum);
stmt.bindChecksum(2, md5Checksum);
stmt.bindLong(3, textBasePath.length());
stmt.exec();
} finally {
stmt.reset();
}
pristinePath = SvnWcDbPristines.getPristineFuturePath(root, sha1Checksum);
/* Ensure any sharding directories exist. */
SVNFileUtil.ensureDirectoryExists(SVNFileUtil.getFileDir(pristinePath));
/*
* Now move the file into the pristine store, overwriting existing files with the same checksum.
*/
SVNFileUtil.rename(tempPath, pristinePath);
}
/* Add the checksums for this text-base to *TEXT_BASES_INFO. */
{
boolean isRevertBase;
File versionedFile = removeSuffix(textBasePath, SVN_WC__REVERT_EXT);
if (versionedFile != null) {
isRevertBase = true;
} else {
versionedFile = removeSuffix(textBasePath, SVN_WC__BASE_EXT);
isRevertBase = false;
}
if (versionedFile == null) {
/*
* Some file that doesn't end with .svn-base or .svn-revert. No idea why that would be in our administrative area, but
* we shouldn't segfault on this case. Note that we already copied this file in the pristine store, but the next
* cleanup will take care of that.
*/
continue;
}
String versionedFileName = SVNFileUtil.getFileName(versionedFile);
/*
* Create a new info for this versioned file, or fill in the existing one if this is the second text-base we've found for it.
*/
TextBaseInfo info = (TextBaseInfo) textBasesInfo.get(versionedFileName);
if (info == null)
info = new TextBaseInfo();
TextBaseFileInfo fileInfo = isRevertBase ? info.revertBase : info.normalBase;
fileInfo.sha1Checksum = sha1Checksum;
fileInfo.md5Checksum = md5Checksum;
textBasesInfo.put(versionedFileName, info);
}
}
return textBasesInfo;
}
/*
* If File name ends with SUFFIX and is longer than SUFFIX, return the part of STR that comes before SUFFIX; else return NULL.
*/
private File removeSuffix(File file, String suffix) {
String fileName = SVNPathUtil.getAbsolutePath(file.getPath());
if (fileName.length() > suffix.length() && fileName.endsWith(suffix)) {
return SVNFileUtil.createFilePath(fileName.substring(0, fileName.length() - suffix.length()));
}
return null;
}
/* Create a physical lock file in the admin directory for ABSPATH. */
private void createPhysicalLock(File absPath) throws SVNException {
File lockAbsPath = buildLockfilePath(absPath);
if (lockAbsPath.isFile()) {
/* Congratulations, we just stole a physical lock from somebody */
return;
}
SVNFileUtil.createEmptyFile(lockAbsPath);
}
/*
* Read the properties from the file at propfileAbsPath, returning them If the propfile is NOT present, then NULL will be returned
*/
/*
* private SVNHashMap readPropFile(File propfileAbsPath) throws SVNException
* { return null; }
*/
/* Read the wcprops from all the files in the admin area of dirAbsPath */
/*
* private SVNHashMap readManyWcProps(File dirAbsPath, SVNAdminArea area)
* throws SVNException { File propsFileAbsPath = SVNWCUtils.admChild(dirAbsPath, WCPROPS_FNAME_FOR_DIR);
*
* SVNHashMap allProps = new SVNHashMap();
*
* SVNHashMap props = readPropFile(propsFileAbsPath); if (props != null) {
* allProps.put("SVN_WC_ENTRY_THIS_DIR", props); }
*
* File propsDirAbsPath = SVNWCUtils.admChild(dirAbsPath, WCPROPS_SUBDIR_FOR_FILES);
* File[] files = SVNFileListUtil.listFiles(propsDirAbsPath);
* for (File file : files) {
* props = readPropFile(file); assert(props != null);
* allProps.put(file.getAbsolutePath(), props);
* }
* return allProps;
* }
*/
/* For wcprops stored in a single file in this working copy */
/*
* private SVNHashMap readWcProps(File dirAbsPath) { return null; }
*/
public static void wipePostUpgrade(SVNWCContext ctx, File dirAbsPath, boolean isWholeAdmin) throws SVNException {
ctx.checkCancelled();
SVNWCAccess access = SVNWCAccess.newInstance(ctx.getEventHandler());
access.setOptions(ctx.getOptions());
ArrayList<File> subDirs = new ArrayList<File>();
boolean isDoDeleteDir = false;
try {
isDoDeleteDir = getVersionedSubdirs(access, dirAbsPath, subDirs, true, true);
} catch (SVNException ex) {
return;
}
for (File childAbsPath : subDirs) {
wipePostUpgrade(ctx, childAbsPath, true);
}
/* ### Should we really be ignoring errors here? */
if (isWholeAdmin)
SVNFileUtil.deleteAll(SVNFileUtil.createFilePath(dirAbsPath, SVNFileUtil.getAdminDirectoryName()), true);
else {
wipeObsoleteFiles(dirAbsPath);
}
if (isDoDeleteDir) {
/*
* If this was a WC-NG single database copy, this directory wouldn't be here (unless it was deleted with --keep-local)
*
* If the directory is empty, we can just delete it; if not we keep it.
*/
SVNFileUtil.deleteAll(dirAbsPath, true);
}
}
public static void wipeObsoleteFiles(File dirAbsPath) throws SVNException {
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, SVNWCContext.WC_ADM_FORMAT), true);
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, SVNWCContext.WC_ADM_ENTRIES), true);
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, ADM_EMPTY_FILE), true);
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, ADM_README), true);
/*
* For formats <= SVN_WC__WCPROPS_MANY_FILES_VERSION, we toss the wcprops for the directory itself, and then all the wcprops for the files.
*/
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, WCPROPS_FNAME_FOR_DIR), true);
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, WCPROPS_SUBDIR_FOR_FILES), true);
/* And for later formats, they are aggregated into one file. */
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, WCPROPS_ALL_DATA), true);
/* Remove the old text-base directory and the old text-base files. */
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, TEXT_BASE_SUBDIR), true);
/* Remove the old properties files... whole directories at a time. */
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, PROPS_SUBDIR), true);
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, PROP_BASE_SUBDIR), true);
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, PROP_WORKING_FOR_DIR), true);
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, PROP_BASE_FOR_DIR), true);
SVNFileUtil.deleteAll(SVNWCUtils.admChild(dirAbsPath, PROP_REVERT_FOR_DIR), true);
/*
* #if 0 ### this checks for a write-lock, and we are not (always) taking out ### a write lock in all callers. *
* SVN_ERR(svn_wc__adm_cleanup_tmp_area(db, wcroot_abspath, iterpool));
* #endif
*/
/* Remove the old-style lock file LAST. */
SVNFileUtil.deleteAll(buildLockfilePath(dirAbsPath), true);
}
/*
* Return the path of the old-school administrative lock file associated with LOCAL_DIR_ABSPATH, allocated from RESULT_POOL.
*/
private static File buildLockfilePath(File dirAbsPath) {
return SVNWCUtils.admChild(dirAbsPath, ADM_LOCK);
}
@SuppressWarnings("unchecked")
private static Map<String, SVNEntry> readEntries(SVNWCAccess access, File localAbsPath) throws SVNException {
Map<String, SVNEntry> entries = null;
try {
SVNAdminArea area = access.probeOpen(localAbsPath, false, 0);
if (!area.getRoot().equals(localAbsPath)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "'{0}' is not versioned directory", localAbsPath);
SVNErrorManager.error(err, SVNLogType.WC);
}
entries = area.getEntries();
} finally {
access.close();
}
return entries;
}
/*
* Return in CHILDREN, the list of all 1.6 versioned subdirectories which also exist on disk as directories. If DELETE_DIR is not NULL set
* DELETE_DIR to TRUE if the directory should be deleted after migrating to WC-NG, otherwise to FALSE. If SKIP_MISSING is TRUE, don't add missing or
* obstructed subdirectories to the list of children.
*/
public static boolean getVersionedSubdirs(SVNWCAccess access, File localAbsPath, ArrayList<File> children, boolean isCalculateDoDeleteDir, boolean isSkipMissing) throws SVNException {
boolean isDoDeleteDir = false;
Map<String, SVNEntry> entries = readEntries(access, localAbsPath);
SVNEntry thisDir = null;
for (Iterator<String> names = entries.keySet().iterator(); names.hasNext();) {
String name = (String) names.next();
SVNEntry entry = (SVNEntry) entries.get(name);
/* skip "this dir" */
if ("".equals(name)) {
thisDir = entry;
continue;
} else if (entry == null || entry.getKind() != SVNNodeKind.DIR) {
continue;
}
File childAbsPath = SVNFileUtil.createFilePath(localAbsPath, name);
if (isSkipMissing) {
SVNNodeKind kind = SVNFileType.getNodeKind(SVNFileType.getType(childAbsPath));
if (kind != SVNNodeKind.DIR)
continue;
}
children.add(childAbsPath);
}
if (isCalculateDoDeleteDir) {
isDoDeleteDir = (thisDir != null && thisDir.isScheduledForDeletion() && !thisDir.isKeepLocal());
}
return isDoDeleteDir;
}
/*
* Return in CHILDREN the list of all versioned *files* in SDB that are
* children of PARENT_RELPATH. These files' existence on disk is not tested.
*
* This set of children is intended for property upgrades. Subdirectory's
* properties exist in the subdirs.
*
* Note that this uses just the SDB to locate children, which means that the
* children must have been upgraded to wc-ng format.
*/
private Set<File> getVersionedFiles(File parentRelPath, SVNSqlJetDb sDb, long wcId) throws SVNException {
Set<File> children = new HashSet<File>();
/* ### just select 'file' children. do we need 'symlink' in the future? */
SVNSqlJetStatement stmt = sDb.getStatement(SVNWCDbStatements.SELECT_ALL_FILES);
try {
stmt.bindLong(1, wcId);
stmt.bindString(2, SVNFileUtil.getFilePath(parentRelPath));
/*
* ### 10 is based on Subversion's average of 8.5 files per versioned directory in its repository. maybe use a different
* value? or count rows first?
*/
while (stmt.next()) {
File localRelPath = SVNFileUtil.createFilePath(stmt.getColumnString(SVNWCDbSchema.NODES__Fields.local_relpath));
if (!children.contains(localRelPath))
children.add(localRelPath);
}
} finally {
stmt.reset();
}
return children;
}
private void migrateProps(File dirAbsPath, SVNWCDbUpgradeData data,
int originalFormat, SVNAdminArea area) throws SVNException {
/*
* General logic here: iterate over all the immediate children of the
* root (since we aren't yet in a centralized system), and for any
* properties that exist, map them as follows:
*
* if (revert props exist): revert -> BASE base -> WORKING working ->
* ACTUAL else if (prop pristine is working [as defined in props.c] ):
* base -> WORKING working -> ACTUAL else: base -> BASE working ->
* ACTUAL
*
* ### the middle "test" should simply look for a WORKING_NODE row
*
* Note that it is legal for "working" props to be missing. That implies
* no local changes to the properties.
*/
String dirAbsPathString = SVNFileUtil.getFilePath(dirAbsPath);
String oldWcRootAbsPath = SVNPathUtil.getCommonPathAncestor(
dirAbsPathString, SVNFileUtil.getFilePath(data.rootAbsPath));
File dirRelPath = new File(SVNPathUtil.getRelativePath(
oldWcRootAbsPath, dirAbsPathString));
/* Migrate the props for "this dir". */
migrateNodeProps(dirAbsPath, data, "", originalFormat, area);
Set<File> children = getVersionedFiles(dirRelPath, data.root.getSDb(),
data.workingCopyId);
/* Iterate over all the files in this SDB. */
for (Iterator<File> files = children.iterator(); files.hasNext();) {
File file = files.next();
migrateNodeProps(dirAbsPath, data, SVNFileUtil.getFileName(file),
originalFormat, area);
}
}
/* Migrate the properties for one node (LOCAL_ABSPATH). */
private void migrateNodeProps(File dirAbsPath, SVNWCDbUpgradeData data,
String name, int originalFormat, SVNAdminArea area)
throws SVNException {
String dirAbsPathString = SVNFileUtil.getFilePath(dirAbsPath);
String oldWcRootAbsPath = SVNPathUtil.getCommonPathAncestor(dirAbsPathString, SVNFileUtil.getFilePath(data.rootAbsPath));
File dirRelPath = new File(SVNPathUtil.getRelativePath(oldWcRootAbsPath, dirAbsPathString));
File basePropsFile = area.getBasePropertiesFile(name, false);
File revertPropsFile = area.getRevertPropertiesFile(name, false);
File propsFile = area.getPropertiesFile(name, false);
SVNProperties baseProps = basePropsFile.isFile() ? new SVNWCProperties(basePropsFile, null).asMap() : null;
SVNProperties revertProps = revertPropsFile.isFile() ? new SVNWCProperties(revertPropsFile, null).asMap() : null;
SVNProperties props = propsFile.isFile() ? new SVNWCProperties(propsFile, null).asMap() : null;
SvnWcDbProperties.upgradeApplyProperties(data.root, data.rootAbsPath,
SVNFileUtil.createFilePath(dirRelPath, name),
baseProps,
props,
revertProps,
originalFormat);
}
}