diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/Compatibility.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/Compatibility.java
index 69ba6e2904..bab090d25a 100644
--- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/Compatibility.java
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/Compatibility.java
@@ -40,7 +40,8 @@ package sonia.scm.repository;
public enum Compatibility
{
NONE(false, false, false, false, false),
- PRE14(true, true, true, true, false), PRE15(false, true, true, true, false),
+ PRE14(true, true, true, true, false),
+ PRE15(false, true, true, true, false),
PRE16(false, false, true, true, false),
PRE17(false, false, false, true, false),
WITH17(false, false, false, false, true);
diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/InstanceIDFix.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/InstanceIDFix.java
new file mode 100644
index 0000000000..2463ba399c
--- /dev/null
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/InstanceIDFix.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+package sonia.scm.repository;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Fixes Subversion repositories with db format 7, but without an instance id in the db/uuid file.
+ *
+ * @see 910
+ * @author Sebastian Sdorra
+ * @since 1.52
+ */
+public final class InstanceIDFix {
+
+ private static final Logger LOG = LoggerFactory.getLogger(InstanceIDFix.class);
+
+ private static final String PATH_DB = "db";
+ private static final String PATH_FORMAT = "format";
+ private static final String PATH_UUID = "uuid";
+
+ private static final String DB_FORMAT = "7";
+ private static final Charset CHARSET = Charsets.US_ASCII;
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ private final File repository;
+ private final String dbFormat;
+ private final List uuids;
+
+ /**
+ * Creates a new instance to fix subversion repositories.
+ *
+ * @param repository root directory of subversion repository
+ *
+ * @throws IOException
+ */
+ public InstanceIDFix(File repository) throws IOException {
+ this.repository = repository;
+ this.dbFormat = readDBFormat(repository);
+ this.uuids = readUUIDS(repository);
+ }
+
+ /**
+ * Returns {@core true} if the repository format is 7 and the instance id is missing.
+ *
+ * @return {@core true} if the repository must be fixed.
+ */
+ public boolean isRequired() {
+ return DB_FORMAT.equals(dbFormat) && uuids.size() == 1;
+ }
+
+ private String readDBFormat(File directory) throws IOException {
+ return Files.readFirstLine(dbFile(directory, PATH_FORMAT), CHARSET);
+ }
+
+ private File dbFile(File directory, String filename) {
+ return new File(directory, PATH_DB + File.separator + filename);
+ }
+
+ private List readUUIDS(File directory) throws IOException {
+ return new ArrayList<>(Files.readLines(dbFile(directory, PATH_UUID), CHARSET));
+ }
+
+ /**
+ * Add missing instance id to the uuid file of the repository and returns the generated instance id.
+ *
+ * @throws IOException
+ * @return generated instance id
+ */
+ public String addInstanceID() throws IOException {
+ Preconditions.checkState(isRequired(), "repository has already an instance id and does not require the fix");
+ String uuid = uuids.get(0);
+ String instanceID = generateInstanceID();
+ addInstanceID(uuid, instanceID);
+ return instanceID;
+ }
+
+ private void addInstanceID(String uuid, String instanceID) throws IOException {
+ LOG.info("created instance id {} for subversion repository {} format 7", instanceID, uuid);
+ String uuidFileContent = createUUIDFileContent(uuid, instanceID);
+ Files.write(uuidFileContent, dbFile(repository, PATH_UUID), CHARSET);
+ uuids.add(instanceID);
+ }
+
+ private String generateInstanceID() {
+ return UUID.randomUUID().toString();
+ }
+
+ private String createUUIDFileContent(String uuid, String instanceID) {
+ return new StringBuilder(uuid)
+ .append(LINE_SEPARATOR)
+ .append(instanceID)
+ .append(LINE_SEPARATOR)
+ .toString();
+
+ }
+
+ @VisibleForTesting
+ List getUuids() {
+ return ImmutableList.copyOf(uuids);
+ }
+
+ @VisibleForTesting
+ File getRepository() {
+ return repository;
+ }
+}
diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java
index e8357b565d..68f1ef99d5 100644
--- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHandler.java
@@ -317,6 +317,8 @@ public class SvnRepositoryHandler
comp.isPre15Compatible(), comp.isPre16Compatible(),
comp.isPre17Compatible(), comp.isWith17Compatible());
+ fixMissingInstanceID(directory);
+
svnRepository = SVNRepositoryFactory.create(url);
String uuid = svnRepository.getRepositoryUUID(true);
@@ -346,6 +348,14 @@ public class SvnRepositoryHandler
SvnUtil.closeSession(svnRepository);
}
}
+
+ private void fixMissingInstanceID(File directory) throws IOException {
+ InstanceIDFix fix = new InstanceIDFix(directory);
+ if (fix.isRequired()) {
+ fix.addInstanceID();
+ }
+ }
+
/**
* Method description
diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/InstanceIDFixTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/InstanceIDFixTest.java
new file mode 100644
index 0000000000..2cf5ae7f28
--- /dev/null
+++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/InstanceIDFixTest.java
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+package sonia.scm.repository;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
+
+/**
+ * Unit tests for {@link InstanceIDFix}.
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class InstanceIDFixTest {
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Test
+ public void testIsRequired() throws SVNException, IOException {
+ // svnkit uses db format 4 as default, so NONE must not be fixed
+ assertFalse(createFix(Compatibility.NONE).isRequired());
+
+ assertFalse(createFix(Compatibility.PRE14).isRequired());
+ assertFalse(createFix(Compatibility.PRE15).isRequired());
+ assertFalse(createFix(Compatibility.PRE16).isRequired());
+
+ // WITH17 creates db format 7, which is subversion >= 1.9 or not?
+ assertTrue(createFix(Compatibility.WITH17).isRequired());
+ }
+
+ @Test
+ public void testAddInstanceID() throws SVNException, IOException {
+ InstanceIDFix fix = createFix(Compatibility.WITH17);
+ assertTrue(fix.isRequired());
+
+ String instanceID = fix.addInstanceID();
+ assertFalse(fix.isRequired());
+
+ fix = new InstanceIDFix(fix.getRepository());
+ assertFalse(fix.isRequired());
+ assertTrue(fix.getUuids().contains(instanceID));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAddInstanceIDAllreadyFixedRepository() throws SVNException, IOException {
+ InstanceIDFix fix = createFix(Compatibility.WITH17);
+ assertTrue(fix.isRequired());
+
+ fix.addInstanceID();
+ fix.addInstanceID();
+ }
+
+ private InstanceIDFix createFix(Compatibility compatibility) throws SVNException, IOException {
+ return new InstanceIDFix(createRepository(compatibility));
+ }
+
+ private File createRepository(Compatibility compatibility) throws SVNException, IOException {
+ File directory = tempFolder.newFolder();
+ SVNRepositoryFactory.createLocalRepository(directory, null, true, false, compatibility.isPre14Compatible(),
+ compatibility.isPre15Compatible(), compatibility.isPre16Compatible(),
+ compatibility.isPre17Compatible(), compatibility.isWith17Compatible());
+ return directory;
+ }
+}
diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java
index 5a46cdfd28..14f0e814fd 100644
--- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java
+++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java
@@ -35,6 +35,8 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.store.StoreFactory;
@@ -43,6 +45,9 @@ import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
/**
*
@@ -66,7 +71,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase
assertTrue(format.isFile());
File db = new File(directory, "db");
-
+
assertTrue(db.exists());
assertTrue(db.isDirectory());
}
@@ -96,4 +101,18 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase
return handler;
}
+
+ @Test
+ public void testCreatedUUID() throws RepositoryException, IOException {
+ SvnRepositoryHandler handler = (SvnRepositoryHandler) getHandler();
+ handler.getConfig().setCompatibility(Compatibility.WITH17);
+
+ Repository repository = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
+ handler.create(repository);
+
+ File directory = handler.getDirectory(repository);
+ File uuidFile = new File(directory, "db" + File.separator + "uuid");
+ List lines = Files.readLines(uuidFile, Charsets.UTF_8);
+ assertEquals(2, lines.size());
+ }
}
diff --git a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java
index 8799485b07..a3a8b90dd1 100644
--- a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java
+++ b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java
@@ -76,6 +76,11 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase
protected abstract RepositoryHandler createRepositoryHandler(
StoreFactory factory, File directory);
+
+ protected RepositoryHandler getHandler() {
+ return handler;
+ }
+
/**
* Method description
*
@@ -202,7 +207,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase
/** Field description */
protected File baseDirectory;
-
+
/** Field description */
private RepositoryHandler handler;
}