package org.tmatesoft.svn.core.internal.wc17.db; import org.tmatesoft.sqljet.core.SqlJetException; import org.tmatesoft.sqljet.core.SqlJetTransactionMode; import org.tmatesoft.sqljet.core.table.ISqlJetCursor; import org.tmatesoft.sqljet.core.table.ISqlJetTable; import org.tmatesoft.sqljet.core.table.SqlJetDb; 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.internal.db.SVNSqlJetDb; import org.tmatesoft.svn.core.internal.db.SVNSqlJetStatement; import org.tmatesoft.svn.core.internal.db.SVNSqlJetTransaction; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileType; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema.NODES__Fields; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbSchema.PRISTINE__Fields; import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbStatements; import org.tmatesoft.svn.core.wc2.SvnChecksum; import org.tmatesoft.svn.util.SVNLogType; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import static org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbStatementUtil.getColumnChecksum; public class SvnWcDbPristines extends SvnWcDbShared { private static final String PRISTINE_STORAGE_EXT = ".svn-base"; private static class RemoveUnreferencedPristine implements SVNSqlJetTransaction { public SvnChecksum sha1_checksum; public File pristineAbsPath; SVNWCDbRoot root; public void transaction(SVNSqlJetDb db) throws SqlJetException, SVNException { SVNSqlJetStatement stmt = db.getStatement(SVNWCDbStatements.DELETE_PRISTINE_IF_UNREFERENCED); try { stmt.bindf("s", sha1_checksum); Long affectedRows = stmt.done(); if (affectedRows > 0) { pristineRemoveFile(true); } } finally { stmt.reset(); } } /* Remove the file at FILE_ABSPATH in such a way that we could re-create a * new file of the same name at any time thereafter. * * On Windows, the file will not disappear immediately from the directory if * it is still being read so the best thing to do is first rename it to a * unique name. */ private void pristineRemoveFile(boolean ignoreEnoent) throws SVNException { if (SVNFileUtil.isWindows) { File temDirAbsPath = getPristineTempDir(root, root.getAbsPath()); File tempAbsPath = SVNFileUtil.createUniqueFile(temDirAbsPath, "pristine", ".tmp", false); try { SVNFileUtil.rename(pristineAbsPath, tempAbsPath); pristineAbsPath = tempAbsPath; } catch (SVNException e){ if (!(ignoreEnoent && SVNFileType.getType(tempAbsPath) == SVNFileType.NONE)) throw e; } } SVNFileUtil.deleteFile(pristineAbsPath); } } public static void cleanupPristine(SVNWCDbRoot root, File localAbsPath) throws SVNException { SVNSqlJetStatement selectList = root.getSDb().getStatement(SVNWCDbStatements.SELECT_UNREFERENCED_PRISTINES); try { while(selectList.next()) { SvnChecksum sha1_checksum = SvnChecksum.fromString(selectList.getColumnString(PRISTINE__Fields.checksum)); removePristineIfUnreferenced(root, localAbsPath, sha1_checksum); } } finally { selectList.reset(); } } private static void removePristineIfUnreferenced(SVNWCDbRoot root, File localAbsPath, SvnChecksum sha1_checksum) throws SVNException { RemoveUnreferencedPristine rup = new RemoveUnreferencedPristine(); rup.sha1_checksum = sha1_checksum; rup.pristineAbsPath = getPristineFileName(root, sha1_checksum, false); rup.root = root; root.getSDb().runTransaction(rup); } public static File getPristineTempDir(SVNWCDbRoot root, File wcRootAbsPath) throws SVNException { return SVNFileUtil.createFilePath(SVNFileUtil.createFilePath(root.getAbsPath(), SVNFileUtil.getAdminDirectoryName()), ISVNWCDb.PRISTINE_TEMPDIR_RELPATH); } public static File getPristineFuturePath(SVNWCDbRoot root, SvnChecksum sha1Checksum) { return getPristineFileName(root, sha1Checksum, false); } public static File getPristineFileName(SVNWCDbRoot root, SvnChecksum sha1Checksum, boolean createSubdir) { /* ### code is in transition. make sure we have the proper data. */ assert (root != null); assert (sha1Checksum != null); assert (sha1Checksum.getKind() == SvnChecksum.Kind.sha1); /* * ### need to fix this to use a symbol for ".svn". we don't need ### to * use join_many since we know "/" is the separator for ### internal * canonical paths */ File base_dir_abspath = SVNFileUtil.createFilePath(SVNFileUtil.createFilePath(root.getAbsPath(), SVNFileUtil.getAdminDirectoryName()), ISVNWCDb.PRISTINE_STORAGE_RELPATH); String hexdigest = sha1Checksum.getDigest(); /* We should have a valid checksum and (thus) a valid digest. */ assert (hexdigest != null); /* Get the first two characters of the digest, for the subdir. */ String subdir = hexdigest.substring(0, 2); if (createSubdir) { File subdirAbspath = SVNFileUtil.createFilePath(base_dir_abspath, subdir); subdirAbspath.mkdirs(); /* * Whatever error may have occurred... ignore it. Typically, this * will be "directory already exists", but if it is something * different*, then presumably another error will follow when we try * to access the file within this (missing?) pristine subdir. */ } /* The file is located at DIR/.svn/pristine/XX/XXYYZZ... */ return SVNFileUtil.createFilePath(SVNFileUtil.createFilePath(base_dir_abspath, subdir), hexdigest + PRISTINE_STORAGE_EXT); } public static boolean checkPristine(SVNWCDbRoot root, SvnChecksum sha1Checksum) throws SVNException { boolean haveRow = false; SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.SELECT_PRISTINE_MD5_CHECKSUM); stmt.bindf("s", sha1Checksum); try { haveRow = stmt.next(); } finally { stmt.reset(); } if (haveRow) { File pristineAbspath = getPristineFileName(root, sha1Checksum, false); SVNNodeKind kindOnDisk = SVNFileType.getNodeKind(SVNFileType.getType(pristineAbspath)); if (kindOnDisk != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_DB_ERROR, "The pristine text with checksum ''{0}'' was found in the DB but not on disk", sha1Checksum); SVNErrorManager.error(err, SVNLogType.WC); } } return haveRow; } public static SvnChecksum getPristineSHA1(SVNWCDbRoot root, SvnChecksum md5Checksum) throws SVNException { SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.SELECT_PRISTINE_SHA1_CHECKSUM); try { stmt.bindChecksum(1, md5Checksum); boolean have_row = stmt.next(); if (!have_row) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_DB_ERROR, "The pristine text with MD5 checksum ''{0}'' not found", md5Checksum.toString()); SVNErrorManager.error(err, SVNLogType.WC); return null; } SvnChecksum sha1_checksum = getColumnChecksum(stmt, PRISTINE__Fields.checksum); assert (sha1_checksum.getKind() == SvnChecksum.Kind.sha1); return sha1_checksum; } finally { stmt.reset(); } } public static File getPristinePath(SVNWCDbRoot root, SvnChecksum sha1Checksum) throws SVNException { boolean present = checkPristine(root, sha1Checksum); if (!present) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_DB_ERROR, "Pristine text not found"); SVNErrorManager.error(err, SVNLogType.WC); } return getPristineFileName(root, sha1Checksum, false); } public static void removePristine(SVNWCDbRoot root, SvnChecksum sha1Checksum) throws SVNException { SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.LOOK_FOR_WORK); boolean haveRow; try { haveRow = stmt.next(); } finally { stmt.reset(); } if (haveRow) { return; } pristineRemove(root, sha1Checksum); } private static void pristineRemove(SVNWCDbRoot root, SvnChecksum sha1Checksum) throws SVNException { SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.DELETE_PRISTINE); try { stmt.bindChecksum(1, sha1Checksum); if (stmt.done() != 0) { File pristineAbspath = getPristineFileName(root, sha1Checksum, true); SVNFileUtil.deleteFile(pristineAbspath); } } finally { stmt.reset(); } } public static void installPristine(SVNWCDbRoot root, File tempfileAbspath, SvnChecksum sha1Checksum, SvnChecksum md5Checksum) throws SVNException { File pristineAbspath = getPristineFileName(root, sha1Checksum, true); if (pristineAbspath.isFile()) { SVNFileUtil.deleteFile(tempfileAbspath); return; } SVNFileUtil.rename(tempfileAbspath, pristineAbspath); long size = pristineAbspath.length(); SVNSqlJetStatement stmt = root.getSDb().getStatement(SVNWCDbStatements.INSERT_PRISTINE); try { stmt.bindChecksum(1, sha1Checksum); stmt.bindChecksum(2, md5Checksum); stmt.bindLong(3, size); stmt.done(); } finally { stmt.reset(); } } public static InputStream readPristine(SVNWCDbRoot root, File wcRootAbsPath, SvnChecksum sha1Checksum) throws SVNException { File pristine_abspath = getPristineFileName(root, sha1Checksum, false); return SVNFileUtil.openFileForReading(pristine_abspath); } public static void fixPristinesRefCount(SVNWCDbRoot root) throws SVNException { Map<String, Long> refcountTable = new HashMap<String, Long>(); begingWriteTransaction(root); try { ISqlJetTable nodesTable = root.getSDb().getDb().getTable(SVNWCDbSchema.NODES.toString()); ISqlJetCursor cursor = nodesTable.open(); try { while(!cursor.eof()) { long opDepth = cursor.getInteger(NODES__Fields.op_depth.toString()); if (opDepth == 0) { String checksum = cursor.getString(NODES__Fields.checksum.toString()); if (refcountTable.containsKey(checksum)) { refcountTable.put(checksum, refcountTable.get(checksum) + 1); } else { refcountTable.put(checksum, 1l); } } cursor.next(); } } finally { cursor.close(); } ISqlJetTable pTable = root.getSDb().getDb().getTable(SVNWCDbSchema.PRISTINE.toString()); for (Iterator<String> checksums = refcountTable.keySet().iterator(); checksums.hasNext();) { String checksum = checksums.next(); cursor = pTable.lookup(null, checksum); try { if (!cursor.eof()) { long refCount = cursor.getInteger(PRISTINE__Fields.refcount.toString()); if (refCount != refcountTable.get(checksum)) { Map<String, Object> value = new HashMap<String, Object>(); cursor.updateByFieldNames(value); } checksums.remove(); } } finally { cursor.close(); } } } catch (SqlJetException e) { rollbackTransaction(root); SVNSqlJetDb.createSqlJetError(e); } finally { commitTransaction(root); } } public static void checkPristineChecksumRefcounts(SVNWCDbRoot root) throws SVNException { final Map<SvnChecksum, Integer> correctChecksumRefcounts = calculateCorrectChecksumRefcounts(root); final Map<SvnChecksum, Integer> checksumRefcountsFromTable = loadChecksumsRefcountsFromTable(root); for (Map.Entry<SvnChecksum, Integer> entry : checksumRefcountsFromTable.entrySet()) { final SvnChecksum sha1Checksum = entry.getKey(); final Integer refCountInteger = entry.getValue(); final Integer correctRefCountInteger = correctChecksumRefcounts.get(sha1Checksum); final int refCount = refCountInteger == null ? 0 : refCountInteger; final int correctRefCount = correctRefCountInteger == null ? 0 : correctRefCountInteger; if (correctRefCount != refCount) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT, "Working copy ''{0}'' is corrupted: {1} table contains incorrect ''refcount'' value {2} for checksum {3} (instead of {4})", root.getAbsPath().getAbsolutePath().replace('/', File.separatorChar), SVNWCDbSchema.PRISTINE.name(), refCount, sha1Checksum, correctRefCount); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } for (Map.Entry<SvnChecksum, Integer> entry : correctChecksumRefcounts.entrySet()) { final SvnChecksum sha1Checksum = entry.getKey(); if (!checksumRefcountsFromTable.containsKey(sha1Checksum)) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT, "Working copy ''{0}'' is corrupted: checksum {1} that is present in {2} table is not listed in {3} table", root.getAbsPath().getAbsolutePath().replace('/', File.separatorChar), sha1Checksum, SVNWCDbSchema.NODES.name(), SVNWCDbSchema.PRISTINE.name()); SVNErrorManager.error(errorMessage, SVNLogType.WC); } } } private static Map<SvnChecksum, Integer> calculateCorrectChecksumRefcounts(SVNWCDbRoot root) throws SVNException { Map<SvnChecksum, Integer> checksumToRefCount = new HashMap<SvnChecksum, Integer>(); final SqlJetDb db = root.getSDb().getDb(); try { final ISqlJetTable nodesTable = db.getTable(SVNWCDbSchema.NODES.name()); db.beginTransaction(SqlJetTransactionMode.READ_ONLY); final ISqlJetCursor cursor = nodesTable.open(); for (; !cursor.eof(); cursor.next()) { String sha1ChecksumString = cursor.getString(SVNWCDbSchema.NODES__Fields.checksum.name()); if (sha1ChecksumString == null) { continue; } SvnChecksum sha1Checksum = SvnChecksum.fromString(sha1ChecksumString); Integer refCount = checksumToRefCount.get(sha1Checksum); int incrementedRefCount = refCount == null ? 1 : refCount + 1; checksumToRefCount.put(sha1Checksum, incrementedRefCount); } cursor.close(); } catch (SqlJetException e) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_DB_ERROR, e); SVNErrorManager.error(errorMessage, e, SVNLogType.WC); } finally { try { db.commit(); } catch (SqlJetException ignore) { } } return checksumToRefCount; } private static Map<SvnChecksum, Integer> loadChecksumsRefcountsFromTable(SVNWCDbRoot root) throws SVNException { Map<SvnChecksum, Integer> checksumToRefCount = new HashMap<SvnChecksum, Integer>(); final SqlJetDb db = root.getSDb().getDb(); try { final ISqlJetTable pristineTable = db.getTable(SVNWCDbSchema.PRISTINE.name()); db.beginTransaction(SqlJetTransactionMode.READ_ONLY); final ISqlJetCursor cursor = pristineTable.open(); for (; !cursor.eof(); cursor.next()) { String sha1ChecksumString = cursor.getString(PRISTINE__Fields.checksum.name()); if (sha1ChecksumString == null) { continue; } SvnChecksum sha1Checksum = SvnChecksum.fromString(sha1ChecksumString); long refcount = cursor.getInteger(PRISTINE__Fields.refcount.name()); checksumToRefCount.put(sha1Checksum, (int)refcount); } cursor.close(); } catch (SqlJetException e) { SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.WC_DB_ERROR, e); SVNErrorManager.error(errorMessage, e, SVNLogType.WC); } finally { try { db.commit(); } catch (SqlJetException ignore) { } } return checksumToRefCount; } public static void transferPristine(SVNWCDb db, File srcLocalAbsPath, File dstWriAbsPath) throws SVNException { SVNWCDb.DirParsedInfo srcParsed = db.parseDir(srcLocalAbsPath, SVNSqlJetDb.Mode.ReadOnly); SVNWCDb.verifyDirUsable(srcParsed.wcDbDir); SVNWCDb.DirParsedInfo dstParsed = db.parseDir(dstWriAbsPath, SVNSqlJetDb.Mode.ReadOnly); SVNWCDb.verifyDirUsable(dstParsed.wcDbDir); if (srcParsed.wcDbDir.getWCRoot() == dstParsed.wcDbDir.getWCRoot() && srcParsed.wcDbDir.getWCRoot().getSDb() == dstParsed.wcDbDir.getWCRoot().getSDb()) { return; } PristineTransfer pristineTransfer = new PristineTransfer(); pristineTransfer.srcWcRoot = srcParsed.wcDbDir.getWCRoot(); pristineTransfer.dstWcRoot = dstParsed.wcDbDir.getWCRoot(); pristineTransfer.srcRelPath = srcParsed.localRelPath; srcParsed.wcDbDir.getWCRoot().getSDb().runTransaction(pristineTransfer); } private static class PristineTransfer implements SVNSqlJetTransaction { public SVNWCDbRoot srcWcRoot; public SVNWCDbRoot dstWcRoot; public File srcRelPath; public void transaction(SVNSqlJetDb db) throws SqlJetException, SVNException { SVNSqlJetStatement stmt = db.getStatement(SVNWCDbStatements.SELECT_COPY_PRISTINES); try { stmt.bindf("is", srcWcRoot.getWcId(), srcRelPath); boolean gotRow = stmt.next(); while (gotRow) { SvnChecksum checksum = SvnWcDbStatementUtil.getColumnChecksum(stmt, NODES__Fields.checksum); SvnChecksum md5Checksum = SvnWcDbStatementUtil.getColumnChecksum(stmt.getJoinedStatement(SVNWCDbSchema.PRISTINE), PRISTINE__Fields.md5_checksum); long size = stmt.getJoinedStatement(SVNWCDbSchema.PRISTINE).getColumnLong(PRISTINE__Fields.size); maybeTransferOnePristine(srcWcRoot, dstWcRoot, checksum, md5Checksum, size); gotRow = stmt.next(); } } finally { stmt.reset(); } } } private static void maybeTransferOnePristine(SVNWCDbRoot srcWcRoot, SVNWCDbRoot dstWcRoot, SvnChecksum checksum, SvnChecksum md5Checksum, long size) throws SVNException { SVNSqlJetStatement stmt = dstWcRoot.getSDb().getStatement(SVNWCDbStatements.INSERT_OR_IGNORE_PRISTINE); try { stmt.bindChecksum(1, checksum); stmt.bindChecksum(2, md5Checksum); stmt.bindLong(3, size); long affectedRows = stmt.done(); if (affectedRows == 0) { return; } } finally { stmt.reset(); } File srcAbsPath = getPristineFileName(srcWcRoot, checksum, false); File dstAbsPath = getPristineFileName(dstWcRoot, checksum, true); SVNFileUtil.copy(srcAbsPath, dstAbsPath, true, false); } }