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