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