/*
* ====================================================================
* 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.io;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.regex.Pattern;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNRevisionProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSFS;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepresentationCacheUtil;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNUUIDGenerator;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileListUtil;
import org.tmatesoft.svn.core.internal.wc.SVNFileType;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.SVNWCProperties;
import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbStatements;
import org.tmatesoft.svn.util.SVNLogType;
/**
* <b>SVNRepositoryFactory</b> is an abstract factory that is responsible
* for creating an appropriate <b>SVNRepository</b> driver specific for the
* protocol to use.
*
* <p>
* Depending on what protocol a user exactly would like to use
* to access the repository he should first of all set up an
* appropriate extension of this factory. So, if the user is going to
* work with the repository via the custom <i>svn</i>-protocol (or
* <i>svn+xxx</i>) he initially calls:
* <pre class="javacode">
* ...
* <span class="javakeyword">import</span> org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
* ...
* <span class="javacomment">//do it once in your application prior to using the library</span>
* <span class="javacomment">//enables working with a repository via the svn-protocol (over svn and svn+ssh)</span>
* SVNRepositoryFactoryImpl.setup();
* ...</pre><br />
* From this point the <b>SVNRepositoryFactory</b> knows how to create
* <b>SVNRepository</b> instances specific for the <i>svn</i>-protocol.
* And further on the user can create an <b>SVNRepository</b> instance:
* <pre class="javacode">
* ...
* <span class="javacomment">//creating a new SVNRepository instance</span>
* String url = <span class="javastring">"svn://host/path"</span>;
* SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(url));
* ...</pre><br />
*
* <table cellpadding="3" cellspacing="1" border="0" width="70%" bgcolor="#999933">
* <tr bgcolor="#ADB8D9" align="left">
* <td><b>Supported Protocols</b></td>
* <td><b>Factory to setup</b></td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>svn://, svn+xxx://</td><td>SVNRepositoryFactoryImpl (<b>org.tmatesoft.svn.core.internal.io.svn</b>)</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>http://, https://</td><td>DAVRepositoryFactory (<b>org.tmatesoft.svn.core.internal.io.dav</b>)</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>file:/// (FSFS only)</td><td>FSRepositoryFactory (<b>org.tmatesoft.svn.core.internal.io.fs</b>)</td>
* </tr>
* </table>
*
* <p>
* Also <b>SVNRepositoryFactory</b> may be used to create local
* FSFS-type repositories.
*
* @version 1.3
* @author TMate Software Ltd.
* @since 1.2
* @see SVNRepository
* @see <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a>
*/
public abstract class SVNRepositoryFactory {
private static final Map myFactoriesMap = new SVNHashMap();
private static final String REPOSITORY_TEMPLATE_PATH = "/org/tmatesoft/svn/core/io/repository/template.jar";
static {
FSRepositoryFactory.setup();
SVNRepositoryFactoryImpl.setup();
DAVRepositoryFactory.setup();
}
protected static void registerRepositoryFactory(String protocol, SVNRepositoryFactory factory) {
if (protocol != null && factory != null) {
synchronized (myFactoriesMap) {
myFactoriesMap.put(protocol, factory);
}
}
}
protected static boolean hasRepositoryFactory(String protocol) {
if (protocol != null) {
synchronized (myFactoriesMap) {
return myFactoriesMap.get(protocol) != null;
}
}
return false;
}
/**
* Creates an <b>SVNRepository</b> driver according to the protocol that is to be
* used to access a repository.
*
* <p>
* The protocol is defined as the beginning part of the URL schema. Currently
* SVNKit supports only <i>svn://</i> (<i>svn+ssh://</i>) and <i>http://</i> (<i>https://</i>)
* schemas.
*
* <p>
* The created <b>SVNRepository</b> driver can later be <i>"reused"</i> for another
* location - that is you can switch it to another repository url not to
* create yet one more <b>SVNRepository</b> object. Use the {@link SVNRepository#setLocation(SVNURL, boolean) SVNRepository.setLocation()}
* method for this purpose.
*
* <p>
* An <b>SVNRepository</b> driver created by this method uses a default
* session options driver ({@link ISVNSession#DEFAULT}) which does not
* allow to keep a single socket connection opened and commit log messages
* caching.
*
* @param url a repository location URL
* @return a protocol specific <b>SVNRepository</b> driver
* @throws SVNException if there's no implementation for the specified protocol
* (the user may have forgotten to register a specific
* factory that creates <b>SVNRepository</b>
* instances for that protocol or the SVNKit
* library does not support that protocol at all)
* @see #create(SVNURL, ISVNSession)
* @see SVNRepository
*
*/
public static SVNRepository create(SVNURL url) throws SVNException {
return create(url, null);
}
/**
* Creates an <b>SVNRepository</b> driver according to the protocol that is to be
* used to access a repository.
*
* <p>
* The protocol is defined as the beginning part of the URL schema. Currently
* SVNKit supports only <i>svn://</i> (<i>svn+ssh://</i>) and <i>http://</i> (<i>https://</i>)
* schemas.
*
* <p>
* The created <b>SVNRepository</b> driver can later be <i>"reused"</i> for another
* location - that is you can switch it to another repository url not to
* create yet one more <b>SVNRepository</b> object. Use the {@link SVNRepository#setLocation(SVNURL, boolean) SVNRepository.setLocation()}
* method for this purpose.
*
* <p>
* This method allows to customize a session options driver for an <b>SVNRepository</b> driver.
* A session options driver must implement the <b>ISVNSession</b> interface. It manages socket
* connections - says whether an <b>SVNRepository</b> driver may use a single socket connection
* during the runtime, or it should open a new connection per each repository access operation.
* And also a session options driver may cache and provide commit log messages during the
* runtime.
*
* @param url a repository location URL
* @param options a session options driver
* @return a protocol specific <b>SVNRepository</b> driver
* @throws SVNException if there's no implementation for the specified protocol
* (the user may have forgotten to register a specific
* factory that creates <b>SVNRepository</b>
* instances for that protocol or the SVNKit
* library does not support that protocol at all)
* @see #create(SVNURL)
* @see SVNRepository
*/
public static SVNRepository create(SVNURL url, ISVNSession options) throws SVNException {
String urlString = url.toString();
synchronized (myFactoriesMap) {
for(Iterator keys = myFactoriesMap.keySet().iterator(); keys.hasNext();) {
String key = (String) keys.next();
if (Pattern.matches(key, urlString)) {
return ((SVNRepositoryFactory) myFactoriesMap.get(key)).createRepositoryImpl(url, options);
}
}
}
if ("file".equalsIgnoreCase(url.getProtocol())) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_LOCAL_REPOS_OPEN_FAILED, "Unable to open an ra_local session to URL");
SVNErrorManager.error(err, SVNLogType.NETWORK);
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_URL, "Unable to create SVNRepository object for ''{0}''", url);
SVNErrorManager.error(err, SVNLogType.NETWORK);
return null;
}
/**
* Creates a local blank FSFS-type repository.
* A call to this routine is equivalent to
* <code>createLocalRepository(path, null, enableRevisionProperties, force)</code>.
*
* @param path a repository root location
* @param enableRevisionProperties enables or not revision property
* modifications
* @param force forces operation to run
* @return a local URL (file:///) of a newly
* created repository
* @throws SVNException
* @see #createLocalRepository(File, String, boolean, boolean)
* @since 1.1
*/
public static SVNURL createLocalRepository(File path, boolean enableRevisionProperties, boolean force) throws SVNException {
return createLocalRepository(path, null, enableRevisionProperties, force);
}
/**
* Creates a local blank FSFS-type repository. This is just similar to
* the Subversion's command: <code>svnadmin create --fs-type=fsfs REPOS_PATH</code>.
* The resultant repository is absolutely format-compatible with Subversion.
*
* <p>
* If <code>uuid</code> is <span class="javakeyword">null</span> or not 36 chars
* wide, the method generates a new UUID for the repository. This UUID would have
* the same format as if it's generated by Subversion itself.
*
* <p>
* If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>
* then the method creates a <code>pre-revprop-change</code> executable file inside
* the <code>"hooks"</code> subdir of the repository tree. This executable file
* simply returns 0 thus allowing revision property modifications, which are not
* permitted, unless one puts such a hook into that very directory.
*
* <p>
* If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
* exists, deletes that path and creates a repository in its place.
*
* <p>
* A call to this routine is equivalent to
* <code>createLocalRepository(path, uuid, enableRevisionProperties, force, false)</code>.
*
* @param path a repository root location
* @param uuid a repository's uuid
* @param enableRevisionProperties enables or not revision property
* modifications
* @param force forces operation to run
* @return a local URL (file:///) of a newly
* created repository
* @throws SVNException
* @see #createLocalRepository(File, String, boolean, boolean, boolean)
* @since 1.1
*/
public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties, boolean force) throws SVNException {
return createLocalRepository(path, uuid, enableRevisionProperties, force, false);
}
/**
* Creates a local blank FSFS-type repository. This is just similar to
* the Subversion's command: <code>svnadmin create --fs-type=fsfs REPOS_PATH</code>.
* The resultant repository is absolutely format-compatible with Subversion.
*
* <p>
* If <code>uuid</code> is <span class="javakeyword">null</span> or not 36 chars
* wide, the method generates a new UUID for the repository. This UUID would have
* the same format as if it's generated by Subversion itself.
*
* <p>
* If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>
* then the method creates a <code>pre-revprop-change</code> executable file inside
* the <code>"hooks"</code> subdir of the repository tree. This executable file
* simply returns 0 thus allowing revision property modifications, which are not
* permitted, unless one puts such a hook into that very directory.
*
* <p>
* If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
* exists, deletes that path and creates a repository in its place.
*
* <p>
* Set <code>pre14Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
* to be compatible with pre-1.4 servers.
*
* <p>
* Note: this method is identical to <code>createLocalRepository(path, uuid, enableRevisionProperties, force, pre14Compatible, false)</code>.
*
* @param path a repository root location
* @param uuid a repository's uuid
* @param enableRevisionProperties enables or not revision property
* modifications
* @param force forces operation to run
* @param pre14Compatible <span class="javakeyword">true</span> to
* create a repository with pre-1.4 format
* @return a local URL (file:///) of a newly
* created repository
* @throws SVNException
* @since 1.1.1
* @see #createLocalRepository(File, String, boolean, boolean, boolean, boolean)
*/
public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties, boolean force, boolean pre14Compatible) throws SVNException {
return createLocalRepository(path, uuid, enableRevisionProperties, force, pre14Compatible, false);
}
/**
* Creates a local blank FSFS-type repository. This is just similar to
* the Subversion's command: <code>svnadmin create --fs-type=fsfs REPOS_PATH</code>.
* The resultant repository is absolutely format-compatible with Subversion.
*
* <p>
* If <code>uuid</code> is <span class="javakeyword">null</span> or not 36 chars
* wide, the method generates a new UUID for the repository. This UUID would have
* the same format as if it's generated by Subversion itself.
*
* <p>
* If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>
* then the method creates a <code>pre-revprop-change</code> executable file inside
* the <code>"hooks"</code> subdir of the repository tree. This executable file
* simply returns 0 thus allowing revision property modifications, which are not
* permitted, unless one puts such a hook into that very directory.
*
* <p>
* If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
* exists, deletes that path and creates a repository in its place.
*
* <p>
* Set <code>pre14Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
* to be compatible with pre-1.4 servers.
*
* <p>
* Set <code>pre15Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
* to be compatible with pre-1.5 servers.
*
* <p>
* There must be only one option (either <code>pre14Compatible</code> or <code>pre15Compatible</code>)
* set to <span class="javakeyword">true</span> at a time.
*
* @param path a repository root location
* @param uuid a repository's uuid
* @param enableRevisionProperties enables or not revision property
* modifications
* @param force forces operation to run
* @param pre14Compatible <span class="javakeyword">true</span> to
* create a repository with pre-1.4 format
* @param pre15Compatible <span class="javakeyword">true</span> to
* create a repository with pre-1.5 format
* @return a local URL (file:///) of a newly
* created repository
* @throws SVNException
* @since 1.2
*/
public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties,
boolean force, boolean pre14Compatible, boolean pre15Compatible) throws SVNException {
return createLocalRepository(path, uuid, enableRevisionProperties, force, pre14Compatible, pre15Compatible, false);
}
/**
* Creates a local blank FSFS-type repository. This is just similar to
* the Subversion's command: <code>svnadmin create --fs-type=fsfs REPOS_PATH</code>.
* The resultant repository is absolutely format-compatible with Subversion.
*
* <p>
* If <code>uuid</code> is <span class="javakeyword">null</span> or not 36 chars
* wide, the method generates a new UUID for the repository. This UUID would have
* the same format as if it's generated by Subversion itself.
*
* <p>
* If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>
* then the method creates a <code>pre-revprop-change</code> executable file inside
* the <code>"hooks"</code> subdir of the repository tree. This executable file
* simply returns 0 thus allowing revision property modifications, which are not
* permitted, unless one puts such a hook into that very directory.
*
* <p>
* If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
* exists, deletes that path and creates a repository in its place.
*
* <p>
* Set <code>pre14Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
* to be compatible with pre-1.4 servers.
*
* <p>
* Set <code>pre15Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
* to be compatible with pre-1.5 servers.
*
* <p>
* Set <code>pre16Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
* to be compatible with pre-1.6 servers.
*
* <p>
* There must be only one option (either <code>pre14Compatible</code> or <code>pre15Compatible</code> or
* <code>pre16Compatible</code>) set to <span class="javakeyword">true</span> at a time.
*
* @param path a repository root location
* @param uuid a repository's uuid
* @param enableRevisionProperties enables or not revision property
* modifications
* @param force forces operation to run
* @param pre14Compatible <span class="javakeyword">true</span> to
* create a repository with pre-1.4 format
* @param pre15Compatible <span class="javakeyword">true</span> to
* create a repository with pre-1.5 format
* @param pre16Compatible <span class="javakeyword">true</span> to
* create a repository with pre-1.6 format
* @return a local URL (file:///) of a newly
* created repository
* @throws SVNException
* @since 1.3
*/
public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties,
boolean force, boolean pre14Compatible, boolean pre15Compatible, boolean pre16Compatible) throws SVNException {
return createLocalRepository(path, uuid, enableRevisionProperties,
force, pre14Compatible, pre15Compatible, pre16Compatible, false, false);
}
public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties,
boolean force, boolean pre14Compatible, boolean pre15Compatible, boolean pre16Compatible,
boolean pre17Compatible, boolean with17Compatible) throws SVNException {
SVNFileType fType = SVNFileType.getType(path);
if (fType != SVNFileType.NONE) {
if (fType == SVNFileType.DIRECTORY) {
File[] children = SVNFileListUtil.listFiles(path);
if ( children != null && children.length != 0) {
if (!force) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "''{0}'' already exists; use ''force'' to overwrite existing files", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
} else {
SVNFileUtil.deleteAll(path, true);
}
}
} else {
if (!force) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "''{0}'' already exists; use ''force'' to overwrite existing files", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
} else {
SVNFileUtil.deleteAll(path, true);
}
}
}
//SVNFileUtil.deleteAll(path, true);
// check if path is inside repository
File root = FSFS.findRepositoryRoot(path);
if (root != null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_BAD_ARGS, "''{0}'' is a subdirectory of an existing repository rooted at ''{1}''", new Object[] {path, root});
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (!path.mkdirs() && !path.exists()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Can not create directory ''{0}''", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
InputStream is = SVNRepositoryFactory.class.getResourceAsStream(REPOSITORY_TEMPLATE_PATH);
if (is == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "No repository template found; should be part of SVNKit library jar");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
File jarFile = SVNFileUtil.createUniqueFile(path, ".template", ".jar", true);
OutputStream uuidOS = null;
OutputStream reposFormatOS = null;
OutputStream fsFormatOS = null;
OutputStream txnCurrentOS = null;
OutputStream minUnpacledOS = null;
OutputStream currentOS = null;
try {
copyToFile(is, jarFile);
extract(jarFile, path);
SVNFileUtil.deleteFile(jarFile);
// translate eols.
if (!SVNFileUtil.isWindows) {
translateFiles(path);
translateFiles(new File(path, "conf"));
translateFiles(new File(path, "hooks"));
translateFiles(new File(path, "locks"));
}
// create pre-rev-prop.
if (enableRevisionProperties) {
if (SVNFileUtil.isWindows) {
File hookFile = new File(path, "hooks/pre-revprop-change.bat");
OutputStream os = SVNFileUtil.openFileForWriting(hookFile);
try {
os.write("@echo off".getBytes());
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot create pre-rev-prop-change hook file at ''{0}'': {1}",
new Object[] {hookFile, e.getLocalizedMessage()});
SVNErrorManager.error(err, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(os);
}
} else {
File hookFile = new File(path, "hooks/pre-revprop-change");
OutputStream os = null;
try {
os = SVNFileUtil.openFileForWriting(hookFile);
os.write("#!/bin/sh\nexit 0".getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot create pre-rev-prop-change hook file at ''{0}'': {1}",
new Object[] {hookFile, e.getLocalizedMessage()});
SVNErrorManager.error(err, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(os);
}
SVNFileUtil.setExecutable(hookFile, true);
}
}
// generate and write UUID.
File uuidFile = new File(path, "db/uuid");
if (uuid == null || uuid.length() != 36) {
byte[] uuidBytes = SVNUUIDGenerator.generateUUID();
uuid = SVNUUIDGenerator.formatUUID(uuidBytes);
}
uuid += '\n';
try {
uuidOS = SVNFileUtil.openFileForWriting(uuidFile);
uuidOS.write(uuid.getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Error writing repository UUID to ''{0}''", uuidFile);
err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
int fsFormat = FSFS.DB_FORMAT;
if( FSFS.DB_FORMAT_PRE_17_USE_AS_DEFAULT && !with17Compatible ) {
fsFormat = FSFS.DB_FORMAT_PRE_17;
}
if( pre17Compatible) {
fsFormat = FSFS.DB_FORMAT_PRE_17;
}
if (pre14Compatible) {
File reposFormatFile = new File(path, "format");
try {
reposFormatOS = SVNFileUtil.openFileForWriting(reposFormatFile);
String format = String.valueOf(FSFS.REPOSITORY_FORMAT_LEGACY);
format += '\n';
reposFormatOS.write(format.getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Error writing repository format to ''{0}''", reposFormatFile);
err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
if (pre16Compatible) {
fsFormat = 3;
}
if (pre14Compatible || pre15Compatible) {
if (pre14Compatible) {
fsFormat = 1;
} else if (pre15Compatible) {
fsFormat = 2;
} //else
File fsFormatFile = new File(path, "db/format");
try {
fsFormatOS = SVNFileUtil.openFileForWriting(fsFormatFile);
String format = String.valueOf(fsFormat);
format += '\n';
fsFormatOS.write(format.getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
"Error writing fs format to ''{0}''", fsFormatFile);
err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
File davSandboxDir = new File(path, FSFS.DAV_DIR);
davSandboxDir.mkdir();
} else {
final File currentTxnLockFile = new File(path, "db/" + FSFS.TXN_CURRENT_LOCK_FILE);
SVNFileUtil.createEmptyFile(currentTxnLockFile);
}
long maxFilesPerDir = 0;
if (fsFormat >= FSFS.LAYOUT_FORMAT_OPTION_MINIMAL_FORMAT) {
maxFilesPerDir = FSFS.getDefaultMaxFilesPerDirectory();
File fsFormatFile = new File(path, "db/format");
try {
fsFormatOS = SVNFileUtil.openFileForWriting(fsFormatFile);
String format = String.valueOf(fsFormat) + "\n";
if (maxFilesPerDir > 0) {
File revFileBefore = new File(path, "db/revs/0");
File tmpFile = SVNFileUtil.createUniqueFile(new File(path, "db/revs"), "0", "tmp", true);
SVNFileUtil.rename(revFileBefore, tmpFile);
File shardRevDir = new File(path, "db/revs/0");
shardRevDir.mkdirs();
File revFileAfter = new File(shardRevDir, "0");
SVNFileUtil.rename(tmpFile, revFileAfter);
File revPropFileBefore = new File(path, "db/revprops/0");
tmpFile = SVNFileUtil.createUniqueFile(new File(path, "db/revprops"), "0", "tmp", true);
SVNFileUtil.rename(revPropFileBefore, tmpFile);
File shardRevPropDir = new File(path, "db/revprops/0");
shardRevPropDir.mkdirs();
File revPropFileAfter = new File(shardRevPropDir, "0");
SVNFileUtil.rename(tmpFile, revPropFileAfter);
format += "layout sharded " + String.valueOf(maxFilesPerDir) + "\n";
} else {
format += "layout linear\n";
}
fsFormatOS.write(format.getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Error writing fs format to ''{0}''", fsFormatFile);
err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
String currentFileLine = fsFormat >= FSFS.MIN_NO_GLOBAL_IDS_FORMAT ? "0\n" : "0 1 1\n";
File currentFile = new File(path, "db/" + FSFS.CURRENT_FILE);
currentOS = SVNFileUtil.openFileForWriting(currentFile);
try {
currentOS.write(currentFileLine.getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
"Can not write to ''{0}'' file: {1}",
new Object[]{currentFile.getName(), e.getLocalizedMessage()});
SVNErrorManager.error(err, e, SVNLogType.FSFS);
}
if (fsFormat >= FSFS.MIN_PACKED_FORMAT) {
File minUnpackedFile = new File(path, "db/" + FSFS.MIN_UNPACKED_REV_FILE);
SVNFileUtil.createEmptyFile(minUnpackedFile);
minUnpacledOS = SVNFileUtil.openFileForWriting(minUnpackedFile);
try {
minUnpacledOS.write("0\n".getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
"Can not write to ''{0}'' file: {1}",
new Object[] { minUnpackedFile.getName(), e.getLocalizedMessage() });
SVNErrorManager.error(err, e, SVNLogType.FSFS);
}
}
if (fsFormat >= FSFS.MIN_CURRENT_TXN_FORMAT) {
File txnCurrentFile = new File(path, "db/" + FSFS.TXN_CURRENT_FILE);
SVNFileUtil.createEmptyFile(txnCurrentFile);
txnCurrentOS = SVNFileUtil.openFileForWriting(txnCurrentFile);
try {
txnCurrentOS.write("0\n".getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
"Can not write to ''{0}'' file: {1}",
new Object[] { txnCurrentFile.getName(), e.getLocalizedMessage() });
SVNErrorManager.error(err, e, SVNLogType.FSFS);
}
}
if (fsFormat >= FSFS.MIN_PROTOREVS_DIR_FORMAT) {
File protoRevsDir = new File(path, "db/txn-protorevs");
protoRevsDir.mkdirs();
}
// set creation date.
File rev0File = new File(path, maxFilesPerDir > 0 ? "db/revprops/0/0" : "db/revprops/0");
String date = SVNDate.formatDate(new Date(System.currentTimeMillis()), true);
Map props = new SVNHashMap();
props.put(SVNRevisionProperty.DATE, date);
SVNWCProperties.setProperties(SVNProperties.wrap(props), rev0File, null, SVNWCProperties.SVN_HASH_TERMINATOR);
setSGID(new File(path, FSFS.DB_DIR));
if (fsFormat >= FSFS.MIN_REP_SHARING_FORMAT) {
File repCacheFile = new File(path, FSFS.DB_DIR + "/" + FSFS.REP_CACHE_DB);
FSRepresentationCacheUtil.create(repCacheFile);
}
/* Create the revprops directory. */
if (fsFormat >= FSFS.MIN_PACKED_REVPROP_FORMAT)
{
File minUnpackedRevPropFile = new File(path, FSFS.DB_DIR + "/" + FSFS.MIN_UNPACKED_REV);
SVNFileUtil.writeToFile(minUnpackedRevPropFile, "0\n", "US-ASCII");
File revPropFile = new File(path, FSFS.DB_DIR + "/" + FSFS.REVISION_PROPERTIES_DIR + "/" + FSFS.REVISION_PROPERTIES_DB);
final SVNSqlJetDb revPropDb = SVNSqlJetDb.open( revPropFile, SVNSqlJetDb.Mode.RWCreate );
try{
revPropDb.execStatement(SVNWCDbStatements.REVPROP_CREATE_SCHEMA);
} finally {
revPropDb.close();
}
}
} finally {
SVNFileUtil.closeFile(uuidOS);
SVNFileUtil.closeFile(reposFormatOS);
SVNFileUtil.closeFile(fsFormatOS);
SVNFileUtil.closeFile(txnCurrentOS);
SVNFileUtil.closeFile(minUnpacledOS);
SVNFileUtil.closeFile(currentOS);
SVNFileUtil.deleteFile(jarFile);
}
return SVNURL.fromFile(path);
}
protected abstract SVNRepository createRepositoryImpl(SVNURL url, ISVNSession session);
private static void copyToFile(InputStream is, File dstFile) throws SVNException {
OutputStream os = null;
byte[] buffer = new byte[16*1024];
try {
os = SVNFileUtil.openFileForWriting(dstFile);
while(true) {
int r = is.read(buffer);
if (r < 0) {
break;
} else if (r == 0) {
continue;
}
os.write(buffer, 0, r);
}
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Can not copy repository template file to ''{0}''", dstFile);
err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
SVNErrorManager.error(err, SVNLogType.NETWORK);
} finally {
SVNFileUtil.closeFile(os);
SVNFileUtil.closeFile(is);
}
}
private static void extract(File srcFile, File dst) throws SVNException {
JarInputStream jis = null;
InputStream is = SVNFileUtil.openFileForReading(srcFile, SVNLogType.NETWORK);
byte[] buffer = new byte[16*1024];
JarFile jarFile = null;
try {
jarFile = new JarFile(srcFile);
jis = new JarInputStream(is);
while(true) {
JarEntry entry = jis.getNextJarEntry();
if (entry == null) {
break;
}
String name = entry.getName();
File entryFile = new File(dst, name);
if (entry.isDirectory()) {
entryFile.mkdirs();
} else {
InputStream fis = null;
OutputStream fos = null;
try {
fis = new BufferedInputStream(jarFile.getInputStream(entry));
fos = SVNFileUtil.openFileForWriting(entryFile);
while(true) {
int r = fis.read(buffer);
if (r < 0) {
break;
} else if (r == 0) {
continue;
}
fos.write(buffer, 0, r);
}
} finally {
SVNFileUtil.closeFile(fos);
SVNFileUtil.closeFile(fis);
}
}
jis.closeEntry();
}
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Can not extract repository files from ''{0}'' to ''{1}''",
new Object[] {srcFile, dst});
err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
SVNErrorManager.error(err, SVNLogType.NETWORK);
} finally {
SVNFileUtil.closeFile(jis);
SVNFileUtil.closeFile(is);
if (jarFile != null) {
try {
jarFile.close();
} catch (IOException e) {
}
}
}
}
private static void translateFiles(File directory) throws SVNException {
File[] children = SVNFileListUtil.listFiles(directory);
byte[] eol = new byte[] {'\n'};
for (int i = 0; children != null && i < children.length; i++) {
File child = children[i];
File tmpChild = null;
if (child.isFile()) {
try {
tmpChild = SVNFileUtil.createUniqueFile(directory, ".repos", ".tmp", true);
SVNTranslator.translate(child, tmpChild, null, eol, null, false, true);
SVNFileUtil.deleteFile(child);
SVNFileUtil.rename(tmpChild, child);
} finally {
SVNFileUtil.deleteFile(tmpChild);
}
}
}
}
private static void setSGID(File dbDir) {
SVNFileUtil.setSGID(dbDir);
File[] dirContents = dbDir.listFiles();
for(int i = 0; dirContents != null && i < dirContents.length; i++) {
File child = dirContents[i];
if (child.isDirectory()) {
setSGID(child);
}
}
}
}