/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.wc.admin; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.*; import org.tmatesoft.svn.core.internal.wc.*; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; import java.io.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; /** * @version 1.3 * @author TMate Software Ltd. */ public class SVNXMLAdminArea extends SVNAdminArea { public static final int WC_FORMAT = SVNXMLAdminAreaFactory.WC_FORMAT; private static final String THIS_DIR = ""; private static final Set BOOLEAN_PROPERTIES = new SVNHashSet(); private static final Set INAPPLICABLE_PROPERTIES = new SVNHashSet(); static { BOOLEAN_PROPERTIES.add(SVNProperty.COPIED); BOOLEAN_PROPERTIES.add(SVNProperty.DELETED); BOOLEAN_PROPERTIES.add(SVNProperty.ABSENT); BOOLEAN_PROPERTIES.add(SVNProperty.INCOMPLETE); INAPPLICABLE_PROPERTIES.add(SVNProperty.CACHABLE_PROPS); INAPPLICABLE_PROPERTIES.add(SVNProperty.PRESENT_PROPS); INAPPLICABLE_PROPERTIES.add(SVNProperty.HAS_PROP_MODS); INAPPLICABLE_PROPERTIES.add(SVNProperty.WORKING_SIZE); INAPPLICABLE_PROPERTIES.add(SVNProperty.DEPTH); INAPPLICABLE_PROPERTIES.add(SVNProperty.HAS_PROPS); INAPPLICABLE_PROPERTIES.add(SVNProperty.KEEP_LOCAL); INAPPLICABLE_PROPERTIES.add(SVNProperty.CHANGELIST); INAPPLICABLE_PROPERTIES.add(SVNProperty.FILE_EXTERNAL_PATH); INAPPLICABLE_PROPERTIES.add(SVNProperty.FILE_EXTERNAL_REVISION); INAPPLICABLE_PROPERTIES.add(SVNProperty.FILE_EXTERNAL_PEG_REVISION); INAPPLICABLE_PROPERTIES.add(SVNProperty.TREE_CONFLICT_DATA); } private File myLockFile; private File myEntriesFile; public SVNXMLAdminArea(File dir) { super(dir); myLockFile = new File(getAdminDirectory(), "lock"); myEntriesFile = new File(getAdminDirectory(), "entries"); } private void saveProperties(SVNLog log) throws SVNException { Map propsCache = getPropertiesStorage(false); if (propsCache == null || propsCache.isEmpty()) { return; } SVNProperties command = new SVNProperties(); for(Iterator entries = propsCache.keySet().iterator(); entries.hasNext();) { String name = (String)entries.next(); SVNVersionedProperties props = (SVNVersionedProperties)propsCache.get(name); if (props.isModified()) { String dstPath = getThisDirName().equals(name) ? "dir-props" : "props/" + name + ".svn-work"; dstPath = getAdminDirectory().getName() + "/" + dstPath; if (props.isEmpty()) { command.put(SVNLog.NAME_ATTR, dstPath); log.addCommand(SVNLog.DELETE, command, false); } else { String tmpPath = "tmp/"; tmpPath += getThisDirName().equals(name) ? "dir-props" : "props/" + name + ".svn-work"; File tmpFile = getAdminFile(tmpPath); String srcPath = getAdminDirectory().getName() + "/" + tmpPath; SVNWCProperties tmpProps = new SVNWCProperties(tmpFile, srcPath); tmpProps.setProperties(props.asMap()); command.put(SVNLog.NAME_ATTR, srcPath); command.put(SVNLog.DEST_ATTR, dstPath); log.addCommand(SVNLog.MOVE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, dstPath); log.addCommand(SVNLog.READONLY, command, false); } props.setModified(false); command.clear(); } } } private void saveBaseProperties(SVNLog log) throws SVNException { Map basePropsCache = getBasePropertiesStorage(false); if (basePropsCache == null || basePropsCache.isEmpty()) { return; } SVNProperties command = new SVNProperties(); for(Iterator entries = basePropsCache.keySet().iterator(); entries.hasNext();) { String name = (String)entries.next(); SVNVersionedProperties props = (SVNVersionedProperties)basePropsCache.get(name); if (props.isModified()) { String dstPath = getThisDirName().equals(name) ? "dir-prop-base" : "prop-base/" + name + ".svn-base"; dstPath = getAdminDirectory().getName() + "/" + dstPath; if (props.isEmpty()) { command.put(SVNLog.NAME_ATTR, dstPath); log.addCommand(SVNLog.DELETE, command, false); } else { String tmpPath = "tmp/"; tmpPath += getThisDirName().equals(name) ? "dir-prop-base" : "prop-base/" + name + ".svn-base"; File tmpFile = getAdminFile(tmpPath); String srcPath = getAdminDirectory().getName() + "/" + tmpPath; SVNWCProperties tmpProps = new SVNWCProperties(tmpFile, srcPath); tmpProps.setProperties(props.asMap()); command.put(SVNLog.NAME_ATTR, srcPath); command.put(SVNLog.DEST_ATTR, dstPath); log.addCommand(SVNLog.MOVE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, dstPath); log.addCommand(SVNLog.READONLY, command, false); } props.setModified(false); command.clear(); } } } public void saveWCProperties(boolean close) throws SVNException { Map wcPropsCache = getWCPropertiesStorage(false); if (wcPropsCache == null) { return; } for(Iterator entries = wcPropsCache.keySet().iterator(); entries.hasNext();) { String name = (String)entries.next(); SVNVersionedProperties props = (SVNVersionedProperties)wcPropsCache.get(name); if (props.isModified()) { String dstPath = getThisDirName().equals(name) ? "dir-wcprops" : "wcprops/" + name + ".svn-work"; File dstFile = getAdminFile(dstPath); if (props.isEmpty()) { SVNFileUtil.deleteFile(dstFile); } else { String tmpPath = "tmp/"; tmpPath += getThisDirName().equals(name) ? "dir-wcprops" : "wcprops/" + name + ".svn-work"; File tmpFile = getAdminFile(tmpPath); SVNWCProperties.setProperties(props.asMap(), dstFile, tmpFile, SVNWCProperties.SVN_HASH_TERMINATOR); } props.setModified(false); } } if (close) { closeWCProperties(); } } public SVNVersionedProperties getBaseProperties(String name) throws SVNException { Map basePropsCache = getBasePropertiesStorage(true); SVNVersionedProperties props = (SVNVersionedProperties)basePropsCache.get(name); if (props != null) { return props; } SVNProperties baseProps = null; try { baseProps = readBaseProperties(name); } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Failed to load properties from disk"); SVNErrorManager.error(err, SVNLogType.WC); } props = new SVNProperties13(baseProps); basePropsCache.put(name, props); return props; } public SVNVersionedProperties getRevertProperties(String name) throws SVNException { Map revertPropsCache = getRevertPropertiesStorage(true); SVNVersionedProperties props = (SVNVersionedProperties)revertPropsCache.get(name); if (props != null) { return props; } SVNProperties revertProps = null; try { revertProps = readRevertProperties(name); } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Failed to load properties from disk"); SVNErrorManager.error(err, SVNLogType.WC); } props = new SVNProperties13(revertProps); revertPropsCache.put(name, props); return props; } public SVNVersionedProperties getProperties(String name) throws SVNException { Map propsCache = getPropertiesStorage(true); SVNVersionedProperties props = (SVNVersionedProperties)propsCache.get(name); if (props != null) { return props; } SVNProperties properties = null; try { properties = readProperties(name); } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Failed to load properties from disk"); SVNErrorManager.error(err, SVNLogType.WC); } props = new SVNProperties13(properties); propsCache.put(name, props); return props; } public SVNVersionedProperties getWCProperties(String name) throws SVNException { Map wcPropsCache = getWCPropertiesStorage(true); SVNVersionedProperties props = (SVNVersionedProperties)wcPropsCache.get(name); if (props != null) { return props; } SVNProperties properties = null; try { properties = readWCProperties(name); } catch (SVNException svne) { SVNErrorMessage err = svne.getErrorMessage().wrap("Failed to load properties from disk"); SVNErrorManager.error(err, SVNLogType.WC); } props = new SVNProperties13(properties); wcPropsCache.put(name, props); return props; } private SVNProperties readProperties(String name) throws SVNException { File propertiesFile = getPropertiesFile(name, false); SVNWCProperties props = new SVNWCProperties(propertiesFile, null); return props.asMap(); } private SVNProperties readBaseProperties(String name) throws SVNException { File propertiesFile = getBasePropertiesFile(name, false); SVNWCProperties props = new SVNWCProperties(propertiesFile, null); return props.asMap(); } private SVNProperties readRevertProperties(String name) throws SVNException { File propertiesFile = getRevertPropertiesFile(name, false); SVNWCProperties props = new SVNWCProperties(propertiesFile, null); return props.asMap(); } private SVNProperties readWCProperties(String name) throws SVNException { String path = getThisDirName().equals(name) ? "dir-wcprops" : "wcprops/" + name + ".svn-work"; File propertiesFile = getAdminFile(path); SVNWCProperties props = new SVNWCProperties(propertiesFile, getAdminDirectory().getName() + "/" + path); return props.asMap(); } public void saveEntries(boolean close) throws SVNException { if (myEntries != null) { SVNEntry rootEntry = (SVNEntry) myEntries.get(getThisDirName()); if (rootEntry == null) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "No default entry in directory ''{0}''", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } String reposURL = rootEntry.getRepositoryRoot(); String url = rootEntry.getURL(); if (reposURL != null && !SVNPathUtil.isAncestor(reposURL, url)) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Entry ''{0}'' has inconsistent repository root and url", getThisDirName()); SVNErrorManager.error(err, SVNLogType.WC); } File tmpFile = new File(getAdminDirectory(), "tmp/entries"); Writer os = null; try { os = new OutputStreamWriter(SVNFileUtil.openFileForWriting(tmpFile), "UTF-8"); writeEntries(os); } catch (IOException e) { SVNFileUtil.closeFile(os); SVNFileUtil.deleteFile(tmpFile); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot write entries file ''{0}'': {1}", new Object[] {myEntriesFile, e.getLocalizedMessage()}); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(os); } SVNFileUtil.rename(tmpFile, myEntriesFile); SVNFileUtil.setReadonly(myEntriesFile, true); if (close) { myEntries = null; } } } public void saveVersionedProperties(SVNLog log, boolean close) throws SVNException { saveProperties(log); saveBaseProperties(log); if (close) { myBaseProperties = null; myProperties = null; } } public void installProperties(String name, SVNProperties baseProps, SVNProperties workingProps, SVNLog log, boolean writeBaseProps, boolean close) throws SVNException { SVNProperties command = new SVNProperties(); SVNNodeKind kind = name.equals(getThisDirName()) ? SVNNodeKind.DIR : SVNNodeKind.FILE; String dstPath = SVNAdminUtil.getPropPath(name, kind, false); if (!workingProps.isEmpty()) { String tmpPath = SVNAdminUtil.getPropPath(name, kind, true); File tmpFile = getFile(tmpPath); SVNWCProperties tmpProps = new SVNWCProperties(tmpFile, tmpPath); if (!workingProps.isEmpty()) { tmpProps.setProperties(workingProps); } else { SVNFileUtil.createEmptyFile(tmpFile); } command.put(SVNLog.NAME_ATTR, tmpPath); command.put(SVNLog.DEST_ATTR, dstPath); log.addCommand(SVNLog.MOVE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, dstPath); log.addCommand(SVNLog.READONLY, command, false); } else { command.put(SVNLog.NAME_ATTR, dstPath); log.addCommand(SVNLog.DELETE, command, false); } command.clear(); if (writeBaseProps) { String basePath = SVNAdminUtil.getPropBasePath(name, kind, false); if (!baseProps.isEmpty()) { String tmpPath = SVNAdminUtil.getPropBasePath(name, kind, true); File tmpFile = getFile(tmpPath); SVNWCProperties tmpProps = new SVNWCProperties(tmpFile, tmpPath); tmpProps.setProperties(baseProps); command.put(SVNLog.NAME_ATTR, tmpPath); command.put(SVNLog.DEST_ATTR, basePath); log.addCommand(SVNLog.MOVE, command, false); command.clear(); command.put(SVNLog.NAME_ATTR, basePath); log.addCommand(SVNLog.READONLY, command, false); } else { command.put(SVNLog.NAME_ATTR, basePath); log.addCommand(SVNLog.DELETE, command, false); } } if (close) { myBaseProperties = null; myProperties = null; } } protected Map fetchEntries() throws SVNException { if (!myEntriesFile.exists()) { return null; } Map entries = new SVNHashMap(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(SVNFileUtil.openFileForReading(myEntriesFile, SVNLogType.WC), "UTF-8")); String line; Map entry = null; while ((line = reader.readLine()) != null) { line = line.trim(); if (line.equals("<entry")) { entry = createEntryHashMap(null); continue; } if (entry != null) { if (line.indexOf('=') <= 0 || line.indexOf('\"') <= 0 || line.indexOf('\"') == line.lastIndexOf('\"')) { continue; } String name = line.substring(0, line.indexOf('=')); String value = line.substring(line.indexOf('\"') + 1, line.lastIndexOf('\"')); value = SVNEncodingUtil.xmlDecode(value); entry.put(SVNProperty.SVN_ENTRY_PREFIX + name, value); if (line.charAt(line.length() - 1) == '>') { String entryName = (String) entry.get(SVNProperty.NAME); if (entryName == null) { return entries; } entry.put(SVNProperty.DEPTH, SVNDepth.INFINITY.getName()); entries.put(entryName, new SVNEntry16(entry, this, entryName)); if (!getThisDirName().equals(entryName)) { SVNEntry rootEntry = (SVNEntry)entries.get(getThisDirName()); if (rootEntry != null) { Map rootEntryAttrs = rootEntry.asMap(); if (entry.get(SVNProperty.REVISION) == null) { entry.put(SVNProperty.REVISION, rootEntryAttrs.get(SVNProperty.REVISION)); } if (entry.get(SVNProperty.URL) == null) { String url = (String) rootEntryAttrs.get(SVNProperty.URL); if (url != null) { url = SVNPathUtil.append(url, SVNEncodingUtil.uriEncode(entryName)); } entry.put(SVNProperty.URL, url); } if (entry.get(SVNProperty.UUID) == null) { entry.put(SVNProperty.UUID, rootEntryAttrs.get(SVNProperty.UUID)); } if (entry.get(SVNProperty.REPOS) == null && rootEntryAttrs.get(SVNProperty.REPOS) != null) { entry.put(SVNProperty.REPOS, rootEntryAttrs.get(SVNProperty.REPOS)); } } } entry = null; } } } } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot read entries file ''{0}'': {1}", new Object[] {myEntriesFile, e.getLocalizedMessage()}); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { SVNFileUtil.closeFile(reader); } return entries; } public String getThisDirName() { return THIS_DIR; } protected void writeEntries(Writer writer) throws IOException, SVNException { SVNEntry rootEntry = (SVNEntry)myEntries.get(getThisDirName()); Map rootEntryAttrs = rootEntry.asMap(); writer.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); writer.write("<wc-entries\n"); writer.write(" xmlns=\"svn:\">\n"); List entryNames = new ArrayList(myEntries.keySet()); Collections.sort(entryNames); for (Iterator entriesIter = entryNames.iterator(); entriesIter.hasNext();) { String name = (String)entriesIter.next(); SVNEntry entry = (SVNEntry)myEntries.get(name); Map entryAttrs = entry.asMap(); writer.write("<entry"); for (Iterator names = entryAttrs.keySet().iterator(); names.hasNext();) { String propName = (String) names.next(); if (!isEntryPropertyApplicable(propName)) { continue; } Object value = entryAttrs.get(propName); if (!(value instanceof String)) { continue; } String propValue = (String) value; if (BOOLEAN_PROPERTIES.contains(propName) && !Boolean.TRUE.toString().equals(propValue)) { continue; } if (!getThisDirName().equals(name)) { Object expectedValue; if (SVNProperty.KIND_DIR.equals(entryAttrs.get(SVNProperty.KIND))) { if (SVNProperty.UUID.equals(propName) || SVNProperty.REVISION.equals(propName) || SVNProperty.URL.equals(propName) || SVNProperty.REPOS.equals(propName)) { continue; } } else { if (SVNProperty.URL.equals(propName)) { expectedValue = SVNPathUtil.append((String) rootEntryAttrs.get(propName), SVNEncodingUtil.uriEncode(name)); } else if (SVNProperty.UUID.equals(propName) || SVNProperty.REVISION.equals(propName)) { expectedValue = rootEntryAttrs.get(propName); } else if (SVNProperty.REPOS.equals(propName)) { expectedValue = rootEntryAttrs.get(propName); } else { expectedValue = null; } if (propValue.equals(expectedValue)) { continue; } } } if (propName == null || !propName.startsWith(SVNProperty.SVN_ENTRY_PREFIX)) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.WC, "attempt to write invalid entry property: " + propName + "=" + propValue); SVNDebugLog.getDefaultLog().logFine(SVNLogType.WC, new Exception()); continue; } propName = propName.substring(SVNProperty.SVN_ENTRY_PREFIX.length()); propValue = SVNEncodingUtil.xmlEncodeAttr(propValue); writer.write("\n "); writer.write(propName); writer.write("=\""); writer.write(propValue); writer.write("\""); } writer.write("/>\n"); writer.flush(); } writer.write("</wc-entries>\n"); writer.flush(); } public boolean hasPropModifications(String name) throws SVNException { File propFile; File baseFile; if (getThisDirName().equals(name)) { propFile = getAdminFile("dir-props"); baseFile = getAdminFile("dir-prop-base"); } else { propFile = getAdminFile("props/" + name + ".svn-work"); baseFile = getAdminFile("prop-base/" + name + ".svn-base"); } SVNEntry entry = getEntry(name, true); long propLength = SVNFileUtil.getFileLength(propFile); boolean propEmtpy = propLength <= 4; if (entry.isScheduledForReplacement()) { return !propEmtpy; } if (propEmtpy) { boolean baseEmtpy = SVNFileUtil.getFileLength(baseFile) <= 4; if (baseEmtpy) { return !propEmtpy; } return true; } if (propLength != SVNFileUtil.getFileLength(baseFile)) { return true; } String realTimestamp = SVNDate.formatDate(new Date(SVNFileUtil.getFileLastModified(propFile))); String fullRealTimestamp = realTimestamp; realTimestamp = realTimestamp.substring(0, 23); String timeStamp = entry.getPropTime(); if (timeStamp != null) { timeStamp = timeStamp.substring(0, 23); if (realTimestamp.equals(timeStamp)) { return false; } } SVNVersionedProperties m1 = getProperties(name); SVNVersionedProperties m2 = getBaseProperties(name); if (m1.equals(m2)) { if (isLocked()) { entry.setPropTime(fullRealTimestamp); saveEntries(false); } return false; } return true; } public boolean hasTextModifications(String name, boolean forceComparison) throws SVNException { SVNFileType fType = SVNFileType.getType(getFile(name)); if (fType == SVNFileType.DIRECTORY || fType == SVNFileType.NONE) { return false; } SVNEntry entry = getEntry(name, true); if (entry.isDirectory()) { return false; } if (!forceComparison) { String textTime = entry.getTextTime(); long textTimeAsLong = SVNDate.parseDateAsMilliseconds(textTime); long tstamp = SVNFileUtil.getFileLastModified(getFile(name)); if (textTimeAsLong == tstamp ) { return false; } } File baseFile = getBaseFile(name, false); if (!baseFile.isFile()) { return true; } // translate versioned file. File baseTmpFile = SVNFileUtil.createUniqueFile(getRoot(), SVNFileUtil.getBasePath(getBaseFile(name, true)), ".tmp", true); File versionedFile = getFile(name); SVNTranslator.translate(this, name, name, SVNFileUtil.getBasePath(baseTmpFile), false); // now compare file and get base file checksum (when forced) MessageDigest digest; boolean equals = true; try { digest = forceComparison ? MessageDigest.getInstance("MD5") : null; equals = SVNFileUtil.compareFiles(baseFile, baseTmpFile, digest); if (forceComparison) { // if checksum differs from expected - throw exception String checksum = SVNFileUtil.toHexDigest(digest); if (!checksum.equals(entry.getChecksum())) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT_TEXT_BASE, "Checksum mismatch indicates corrupt text base: ''{0}''\n" + " expected: {1}\n" + " actual: {2}\n", new Object[] {baseFile, entry.getChecksum(), checksum}); SVNErrorManager.error(err, SVNLogType.WC); } } } catch (NoSuchAlgorithmException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "MD5 implementation not found: {0}", e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.WC); } finally { baseTmpFile.delete(); } if (equals && isLocked()) { entry.setTextTime(SVNDate.formatDate(new Date(SVNFileUtil.getFileLastModified(versionedFile)))); saveEntries(false); } return !equals; } public boolean hasProperties(String entryName) throws SVNException { File propFile; File baseFile; if (getThisDirName().equals(entryName)) { propFile = getAdminFile("dir-props"); baseFile = getAdminFile("dir-prop-base"); } else { propFile = getAdminFile("props/" + entryName + ".svn-work"); baseFile = getAdminFile("prop-base/" + entryName + ".svn-base"); } SVNWCProperties baseProps = new SVNWCProperties(baseFile, null); if (baseProps.isEmpty()) { SVNWCProperties props = new SVNWCProperties(propFile, null); return !props.isEmpty(); } return true; } public boolean lock(boolean stealLock) throws SVNException { if (!isVersioned()) { return false; } if (myLockFile.isFile()) { if (stealLock) { setLocked(true); return true; } SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_LOCKED, "Working copy ''{0}'' locked; try performing ''cleanup''", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } boolean created = false; try { created = myLockFile.createNewFile(); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NOT_LOCKED, "Cannot lock working copy ''{0}'': {1}", new Object[] {getRoot(), e.getLocalizedMessage()}); SVNErrorManager.error(err, e, SVNLogType.WC); } if (!created) { if (myLockFile.isFile()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_LOCKED, "Working copy ''{0}'' is locked; try performing 'cleanup'", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NOT_LOCKED, "Cannot lock working copy ''{0}''", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } } setLocked(true); return created; } boolean innerLock() throws SVNException { if (myLockFile.isFile()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_LOCKED, "Working copy ''{0}'' locked; try performing ''cleanup''", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } boolean created = false; try { created = myLockFile.createNewFile(); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NOT_LOCKED, "Cannot lock working copy ''{0}'': {1}", new Object[] {getRoot(), e.getLocalizedMessage()}); SVNErrorManager.error(err, e, SVNLogType.WC); } if (!created) { if (myLockFile.isFile()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_LOCKED, "Working copy ''{0}'' is locked; try performing 'cleanup'", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } else { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NOT_LOCKED, "Cannot lock working copy ''{0}''", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } } return created; } public boolean unlock() throws SVNException { if (!myLockFile.exists()) { return true; } // only if there are not locks or killme files. boolean killMe = getAdminFile("KILLME").exists(); if (killMe) { return false; } File[] logs = SVNFileListUtil.listFiles(getAdminDirectory()); for (int i = 0; logs != null && i < logs.length; i++) { File log = logs[i]; if ("log".equals(log.getName()) || log.getName().startsWith("log.")) { return false; } } boolean deleted = SVNFileUtil.deleteFile(myLockFile); if (!deleted) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_LOCKED, "Failed to unlock working copy ''{0}''", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } return deleted; } public boolean isVersioned() { if (getAdminDirectory().isDirectory() && myEntriesFile.canRead()) { try { if (getEntry(getThisDirName(), false) != null) { return true; } } catch (SVNException e) { // } } return false; } public SVNAdminArea createVersionedDirectory(File dir, String url, String rootURL, String uuid, long revNumber, boolean createMyself, SVNDepth depth) throws SVNException { dir = createMyself ? getRoot() : dir; dir.mkdirs(); File adminDir = createMyself ? getAdminDirectory() : new File(dir, SVNFileUtil.getAdminDirectoryName()); adminDir.mkdir(); SVNFileUtil.setHidden(adminDir, true); // lock dir. File lockFile = createMyself ? myLockFile : new File(adminDir, "lock"); SVNFileUtil.createEmptyFile(lockFile); SVNAdminUtil.createReadmeFile(adminDir); SVNFileUtil.createEmptyFile(createMyself ? getAdminFile("empty-file") : new File(adminDir, "empty-file")); File[] tmp = { createMyself ? getAdminFile("tmp") : new File(adminDir, "tmp"), createMyself ? getAdminFile("tmp" + File.separatorChar + "props") : new File(adminDir, "tmp" + File.separatorChar + "props"), createMyself ? getAdminFile("tmp" + File.separatorChar + "prop-base") : new File(adminDir, "tmp" + File.separatorChar + "prop-base"), createMyself ? getAdminFile("tmp" + File.separatorChar + "text-base") : new File(adminDir, "tmp" + File.separatorChar + "text-base"), createMyself ? getAdminFile("tmp" + File.separatorChar + "wcprops") : new File(adminDir, "tmp" + File.separatorChar + "wcprops"), createMyself ? getAdminFile("props") : new File(adminDir, "props"), createMyself ? getAdminFile("prop-base") : new File(adminDir, "prop-base"), createMyself ? getAdminFile("text-base") : new File(adminDir, "text-base"), createMyself ? getAdminFile("wcprops") : new File(adminDir, "wcprops")}; for (int i = 0; i < tmp.length; i++) { tmp[i].mkdir(); } SVNAdminUtil.createFormatFile(adminDir); SVNAdminArea adminArea = createMyself ? this : new SVNXMLAdminArea(dir); adminArea.setLocked(true); SVNEntry rootEntry = adminArea.getEntry(adminArea.getThisDirName(), true); if (rootEntry == null) { rootEntry = adminArea.addEntry(adminArea.getThisDirName()); } if (url != null) { rootEntry.setURL(url); } rootEntry.setRepositoryRoot(rootURL); rootEntry.setRevision(revNumber); rootEntry.setKind(SVNNodeKind.DIR); if (uuid != null) { rootEntry.setUUID(uuid); } if (revNumber > 0) { rootEntry.setIncomplete(true); } adminArea.saveEntries(false); // unlock dir. SVNFileUtil.deleteFile(lockFile); return adminArea; } public boolean isLocked() { if (!myWasLocked) { return false; } return myLockFile.isFile(); } public int getFormatVersion() { return WC_FORMAT; } protected SVNVersionedProperties formatBaseProperties(SVNProperties srcProperties) { SVNProperties filteredProperties = new SVNProperties(srcProperties); filteredProperties.remove(SVNProperty.MERGE_INFO); return new SVNProperties13(srcProperties); } protected SVNVersionedProperties formatProperties(SVNEntry entry, SVNProperties srcProperties) { SVNProperties filteredProperties = new SVNProperties(srcProperties); filteredProperties.remove(SVNProperty.MERGE_INFO); return new SVNProperties13(filteredProperties); } public void handleKillMe() throws SVNException { boolean killMe = isKillMe(); if (killMe) { SVNEntry entry = getEntry(getThisDirName(), false); long dirRevision = entry != null ? entry.getRevision() : -1; // deleted dir, files and entry in parent. File dir = getRoot(); SVNWCAccess access = getWCAccess(); boolean isWCRoot = access.isWCRoot(getRoot()); try { removeFromRevisionControl(getThisDirName(), true, false); } catch (SVNException svne) { SVNDebugLog.getDefaultLog().logFine(SVNLogType.WC, svne); if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.WC_LEFT_LOCAL_MOD) { throw svne; } } if (isWCRoot) { return; } // compare revision with parent's one SVNAdminArea parentArea = access.retrieve(dir.getParentFile()); SVNEntry parentEntry = parentArea.getEntry(parentArea.getThisDirName(), false); if (dirRevision > parentEntry.getRevision()) { SVNEntry entryInParent = parentArea.addEntry(dir.getName()); entryInParent.setDeleted(true); entryInParent.setKind(SVNNodeKind.DIR); entryInParent.setRevision(dirRevision); parentArea.saveEntries(false); } } } public void postCommit(String fileName, long revisionNumber, boolean implicit, boolean rerun, SVNErrorCode errorCode) throws SVNException { SVNEntry entry = getEntry(fileName, true); if (entry == null || (!getThisDirName().equals(fileName) && entry.getKind() != SVNNodeKind.FILE)) { SVNErrorMessage err = SVNErrorMessage.create(errorCode, "Log command for directory ''{0}'' is mislocated", getRoot()); SVNErrorManager.error(err, SVNLogType.WC); } if (!implicit && entry.isScheduledForDeletion()) { if (getThisDirName().equals(fileName)) { entry.setRevision(revisionNumber); entry.setKind(SVNNodeKind.DIR); File killMe = getAdminFile(ADM_KILLME); if (rerun && killMe.isFile()) { return; } if (killMe.getParentFile().isDirectory()) { try { killMe.createNewFile(); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot create file ''{0}'': {1}", new Object[] {killMe, e.getLocalizedMessage()}); SVNErrorManager.error(err, e, SVNLogType.WC); } } } else { removeFromRevisionControl(fileName, false, false); SVNEntry parentEntry = getEntry(getThisDirName(), true); if (revisionNumber > parentEntry.getRevision()) { SVNEntry fileEntry = addEntry(fileName); fileEntry.setKind(SVNNodeKind.FILE); fileEntry.setDeleted(true); fileEntry.setRevision(revisionNumber); } } return; } if (!implicit && entry.isScheduledForReplacement() && getThisDirName().equals(fileName)) { for (Iterator ents = entries(true); ents.hasNext();) { SVNEntry currentEntry = (SVNEntry) ents.next(); if (!currentEntry.isScheduledForDeletion()) { continue; } if (currentEntry.getKind() == SVNNodeKind.FILE || currentEntry.getKind() == SVNNodeKind.DIR) { removeFromRevisionControl(currentEntry.getName(), false, false); } } } long textTime = 0; if (!implicit && !getThisDirName().equals(fileName)) { File tmpFile = getBaseFile(fileName, true); SVNFileType fileType = SVNFileType.getType(tmpFile); if (fileType == SVNFileType.FILE || fileType == SVNFileType.SYMLINK) { boolean modified = false; File workingFile = getFile(fileName); File tmpFile2 = SVNFileUtil.createUniqueFile(tmpFile.getParentFile(), fileName, ".tmp", true); try { String tmpFile2Path = SVNFileUtil.getBasePath(tmpFile2); SVNTranslator.translate(this, fileName, fileName, tmpFile2Path, false); modified = !SVNFileUtil.compareFiles(tmpFile, tmpFile2, null); } catch (SVNException svne) { SVNErrorMessage err = SVNErrorMessage.create(errorCode, "Error comparing ''{0}'' and ''{1}''", new Object[] {workingFile, tmpFile}); SVNErrorManager.error(err, svne, SVNLogType.WC); } finally { tmpFile2.delete(); } textTime = modified ? SVNFileUtil.getFileLastModified(tmpFile) : SVNFileUtil.getFileLastModified(workingFile); } } if (!implicit && entry.isScheduledForReplacement()) { SVNFileUtil.deleteFile(getBasePropertiesFile(fileName, false)); } long propTime = 0; boolean setReadWrite = false; boolean setNotExecutable = false; SVNVersionedProperties baseProps = getBaseProperties(fileName); SVNVersionedProperties wcProps = getProperties(fileName); //TODO: to work properly we must create a tmp working props file //instead of tmp base props one File tmpPropsFile = getPropertiesFile(fileName, true); File wcPropsFile = getPropertiesFile(fileName, false); File basePropertiesFile = getBasePropertiesFile(fileName, false); SVNFileType tmpPropsType = SVNFileType.getType(tmpPropsFile); // tmp may be missing when there were no prop change at all! if (tmpPropsType == SVNFileType.FILE) { SVNWCProperties working = new SVNWCProperties(wcPropsFile, null); SVNWCProperties workingTmp = new SVNWCProperties(tmpPropsFile, null); SVNProperties pDiff = working.compareTo(workingTmp); boolean equals = pDiff == null || pDiff.isEmpty(); propTime = equals ? SVNFileUtil.getFileLastModified(wcPropsFile) : SVNFileUtil.getFileLastModified(tmpPropsFile); if (!getThisDirName().equals(fileName)) { SVNVersionedProperties propDiff = baseProps.compareTo(wcProps); setReadWrite = propDiff != null && propDiff.containsProperty(SVNProperty.NEEDS_LOCK) && propDiff.getPropertyValue(SVNProperty.NEEDS_LOCK) == null; setNotExecutable = propDiff != null && propDiff.containsProperty(SVNProperty.EXECUTABLE) && propDiff.getPropertyValue(SVNProperty.EXECUTABLE) == null; } try { if (!tmpPropsFile.exists() || tmpPropsFile.length() <= 4) { SVNFileUtil.deleteFile(basePropertiesFile); } else { SVNFileUtil.copyFile(tmpPropsFile, basePropertiesFile, true); SVNFileUtil.setReadonly(basePropertiesFile, true); } } finally { SVNFileUtil.deleteFile(tmpPropsFile); } } else if (entry.getPropTime() == null && !wcProps.isEmpty()) { propTime = SVNFileUtil.getFileLastModified(wcPropsFile); } if (!getThisDirName().equals(fileName) && !implicit) { File tmpFile = getBaseFile(fileName, true); File baseFile = getBaseFile(fileName, false); File wcFile = getFile(fileName); File tmpFile2 = null; try { tmpFile2 = SVNFileUtil.createUniqueFile(tmpFile.getParentFile(), fileName, ".tmp", false); boolean overwritten = false; SVNFileType fileType = SVNFileType.getType(tmpFile); boolean special = getProperties(fileName).getPropertyValue(SVNProperty.SPECIAL) != null; if (!SVNFileUtil.symlinksSupported() || !special) { if (fileType == SVNFileType.FILE) { SVNTranslator.translate(this, fileName, SVNFileUtil.getBasePath(tmpFile), SVNFileUtil.getBasePath(tmpFile2), true); } else { SVNTranslator.translate(this, fileName, fileName, SVNFileUtil.getBasePath(tmpFile2), true); } if (!SVNFileUtil.compareFiles(tmpFile2, wcFile, null)) { SVNFileUtil.copyFile(tmpFile2, wcFile, true); overwritten = true; } } boolean needsReadonly = getProperties(fileName).getPropertyValue(SVNProperty.NEEDS_LOCK) != null && entry.getLockToken() == null; boolean needsExecutable = getProperties(fileName).getPropertyValue(SVNProperty.EXECUTABLE) != null; if (needsReadonly) { SVNFileUtil.setReadonly(wcFile, true); overwritten = true; } if (needsExecutable) { SVNFileUtil.setExecutable(wcFile, true); overwritten = true; } if (fileType == SVNFileType.FILE) { SVNFileUtil.rename(tmpFile, baseFile); } if (setReadWrite) { SVNFileUtil.setReadonly(wcFile, false); overwritten = true; } if (setNotExecutable) { SVNFileUtil.setExecutable(wcFile, false); overwritten = true; } if (overwritten) { textTime = SVNFileUtil.getFileLastModified(wcFile); } } catch (SVNException svne) { SVNErrorMessage err = SVNErrorMessage.create(errorCode, "Error replacing text-base of ''{0}''", fileName); SVNErrorManager.error(err, svne, SVNLogType.WC); } finally { tmpFile2.delete(); tmpFile.delete(); } } // update entry Map entryAttrs = new SVNHashMap(); entryAttrs.put(SVNProperty.REVISION, SVNProperty.toString(revisionNumber)); entryAttrs.put(SVNProperty.KIND, getThisDirName().equals(fileName) ? SVNProperty.KIND_DIR : SVNProperty.KIND_FILE); if (!implicit) { entryAttrs.put(SVNProperty.SCHEDULE, null); } entryAttrs.put(SVNProperty.COPIED, SVNProperty.toString(false)); entryAttrs.put(SVNProperty.DELETED, SVNProperty.toString(false)); if (textTime != 0 && !implicit) { entryAttrs.put(SVNProperty.TEXT_TIME, SVNDate.formatDate(new Date(textTime))); } if (propTime != 0 && !implicit) { entryAttrs.put(SVNProperty.PROP_TIME, SVNDate.formatDate(new Date(propTime))); } entryAttrs.put(SVNProperty.CONFLICT_NEW, null); entryAttrs.put(SVNProperty.CONFLICT_OLD, null); entryAttrs.put(SVNProperty.CONFLICT_WRK, null); entryAttrs.put(SVNProperty.PROP_REJECT_FILE, null); entryAttrs.put(SVNProperty.COPYFROM_REVISION, null); entryAttrs.put(SVNProperty.COPYFROM_URL, null); try { modifyEntry(fileName, entryAttrs, false, true); } catch (SVNException svne) { SVNErrorMessage err = SVNErrorMessage.create(errorCode, "Error modifying entry of ''{0}''", fileName); SVNErrorManager.error(err, svne, SVNLogType.WC); } if (!getThisDirName().equals(fileName)) { return; } // update entry in parent. File dirFile = getRoot(); if (getWCAccess().isWCRoot(getRoot())) { return; } boolean unassociated = false; SVNAdminArea parentArea = null; try { parentArea = getWCAccess().retrieve(dirFile.getParentFile()); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) { parentArea = getWCAccess().open(dirFile.getParentFile(), true, false, 0); unassociated = true; } throw svne; } SVNEntry entryInParent = parentArea.getEntry(dirFile.getName(), false); if (entryInParent != null) { entryAttrs.clear(); if (!implicit) { entryAttrs.put(SVNProperty.SCHEDULE, null); } entryAttrs.put(SVNProperty.COPIED, SVNProperty.toString(false)); entryAttrs.put(SVNProperty.COPYFROM_REVISION, null); entryAttrs.put(SVNProperty.COPYFROM_URL, null); entryAttrs.put(SVNProperty.DELETED, SVNProperty.toString(false)); try { parentArea.modifyEntry(entryInParent.getName(), entryAttrs, true, true); } catch (SVNException svne) { SVNErrorMessage err = SVNErrorMessage.create(errorCode, "Error modifying entry of ''{0}''", fileName); SVNErrorManager.error(err, svne, SVNLogType.WC); } } parentArea.saveEntries(false); if (unassociated) { getWCAccess().closeAdminArea(dirFile.getParentFile()); } } public boolean hasTreeConflict(String name) throws SVNException { return false; } public SVNTreeConflictDescription getTreeConflict(String name) throws SVNException { return null; } public void addTreeConflict(SVNTreeConflictDescription conflict) throws SVNException { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "This feature is not supported in version {0} of working copy format", String.valueOf(getFormatVersion())); SVNErrorManager.error(err, SVNLogType.WC); } public SVNTreeConflictDescription deleteTreeConflict(String name) throws SVNException { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "This feature is not supported in version {0} of working copy format", String.valueOf(getFormatVersion())); SVNErrorManager.error(err, SVNLogType.WC); return null; } public void setFileExternalLocation(String name, SVNURL url, SVNRevision pegRevision, SVNRevision revision, SVNURL reposRootURL) throws SVNException { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "This feature is not supported in version {0} of working copy format", String.valueOf(getFormatVersion())); SVNErrorManager.error(err, SVNLogType.WC); } protected boolean isEntryPropertyApplicable(String propName) { return propName != null && !INAPPLICABLE_PROPERTIES.contains(propName); } protected boolean readExtraOptions(BufferedReader reader, SVNEntry entryAttrs) throws SVNException, IOException { return false; } protected int writeExtraOptions(Writer writer, String entryName, SVNEntry Attrs, int emptyFields) throws SVNException, IOException { return emptyFields; } }