diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c09ec46c..60f1134cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Close file lists in migration ([#1191](https://github.com/scm-manager/scm-manager/pull/1191)) - Use command in javahg.py from registrar (Upgrade to newer javahg version) ([#1192](https://github.com/scm-manager/scm-manager/pull/1192)) - Fixed wrong e-tag format ([sdorra/web-resource #1](https://github.com/sdorra/web-resources/pull/1)) +- Handles repositories in custom directories correctly in migration from 1.x ([#1201](https://github.com/scm-manager/scm-manager/pull/1201)) ## [2.0.0] - 2020-06-04 ### Added diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/BaseMigrationStrategy.java b/scm-webapp/src/main/java/sonia/scm/update/repository/BaseMigrationStrategy.java index ef2624eadd..300a01c2b3 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/BaseMigrationStrategy.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/BaseMigrationStrategy.java @@ -36,10 +36,10 @@ import java.util.stream.Stream; abstract class BaseMigrationStrategy implements MigrationStrategy.Instance { - private final SCMContextProvider contextProvider; + private final V1RepositoryMigrationLocationResolver locationResolver; BaseMigrationStrategy(SCMContextProvider contextProvider) { - this.contextProvider = contextProvider; + this.locationResolver = new V1RepositoryMigrationLocationResolver(contextProvider); } Path getSourceDataPath(String name, String type) { @@ -48,7 +48,7 @@ abstract class BaseMigrationStrategy implements MigrationStrategy.Instance { } Path getTypeDependentPath(String type) { - return contextProvider.getBaseDirectory().toPath().resolve("repositories").resolve(type); + return locationResolver.getTypeDependentPath(type); } void listSourceDirectory(Path sourceDirectory, Consumer> pathConsumer) { diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryMigrationLocationResolver.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryMigrationLocationResolver.java new file mode 100644 index 0000000000..5c3b15cb78 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryMigrationLocationResolver.java @@ -0,0 +1,82 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.update.repository; + +import sonia.scm.SCMContextProvider; +import sonia.scm.migration.UpdateException; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import java.io.File; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; + +class V1RepositoryMigrationLocationResolver { + + private final Map typeDependentPaths = new HashMap<>(); + private final SCMContextProvider contextProvider; + + V1RepositoryMigrationLocationResolver(SCMContextProvider contextProvider) { + this.contextProvider = contextProvider; + typeDependentPaths.put("git", readConfiguredPath(contextProvider, "git", "git.xml")); + typeDependentPaths.put("hg", readConfiguredPath(contextProvider, "hg", "hg.xml")); + typeDependentPaths.put("svn", readConfiguredPath(contextProvider, "svn", "svn.xml")); + } + + Path getTypeDependentPath(String type) { + return typeDependentPaths.computeIfAbsent(type, t -> defaultPath(contextProvider, t)); + } + + private static Path readConfiguredPath(SCMContextProvider contextProvider, String type, String filename) { + return readConfig(contextProvider, filename) + .map(v1RepositoryTypeConfig -> v1RepositoryTypeConfig.getRepositoryDirectory().toPath()) + .orElseGet(() -> defaultPath(contextProvider, type)); + } + + private static Path defaultPath(SCMContextProvider contextProvider, String type) { + return contextProvider.getBaseDirectory().toPath().resolve("repositories").resolve(type); + } + + private static Optional readConfig(SCMContextProvider contextProvider, String filename) { + try { + JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryTypeConfig.class); + File file = new File(new File(contextProvider.getBaseDirectory(), "config"), filename); + if (file.exists()) { + Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(file); + if (unmarshal instanceof V1RepositoryTypeConfig) { + return of((V1RepositoryTypeConfig) unmarshal); + } + } + } catch (JAXBException e) { + throw new UpdateException(String.format("could not read configuration file %s for repository type configuration", filename), e); + } + return empty(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryTypeConfig.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryTypeConfig.java new file mode 100644 index 0000000000..8e0734486e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryTypeConfig.java @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.update.repository; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.File; + +@XmlRootElement(name = "config") +@XmlAccessorType(XmlAccessType.FIELD) +public class V1RepositoryTypeConfig { + + private File repositoryDirectory; + + public File getRepositoryDirectory() { + return repositoryDirectory; + } +} + diff --git a/scm-webapp/src/test/java/sonia/scm/update/repository/V1RepositoryMigrationLocationResolverTest.java b/scm-webapp/src/test/java/sonia/scm/update/repository/V1RepositoryMigrationLocationResolverTest.java new file mode 100644 index 0000000000..98dcaf9192 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/update/repository/V1RepositoryMigrationLocationResolverTest.java @@ -0,0 +1,140 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.update.repository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.SCMContextProvider; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class V1RepositoryMigrationLocationResolverTest { + + @Mock + SCMContextProvider contextProvider; + + private Path scmBaseDirectory; + private Path configDirectory; + + @BeforeEach + void initContext(@TempDir Path temp) throws IOException { + scmBaseDirectory = temp.resolve("scm"); + configDirectory = scmBaseDirectory.resolve("config"); + Files.createDirectories(configDirectory); + when(contextProvider.getBaseDirectory()).thenReturn(scmBaseDirectory.toFile()); + } + + @Test + void shouldReturnDefaultPathIfNothingIsConfigured() { + V1RepositoryMigrationLocationResolver resolver = new V1RepositoryMigrationLocationResolver(contextProvider); + + Path path = resolver.getTypeDependentPath("git"); + + assertThat(path).isEqualTo(scmBaseDirectory.resolve("repositories").resolve("git")); + } + + @Test + void shouldReturnCustomPathIfAnotherPathIsConfiguredForGit(@TempDir Path temp) throws IOException { + Path otherGitDirectory = temp.resolve("other"); + Files.write(configDirectory.resolve("git.xml"), + asList("", + "", + " false", + " " + otherGitDirectory + "", + " ", + " false", + "" + ) + ); + + Path path = new V1RepositoryMigrationLocationResolver(contextProvider).getTypeDependentPath("git"); + + assertThat(path).isEqualTo(otherGitDirectory); + } + + @Test + void shouldReturnCustomPathIfAnotherPathIsConfiguredForHg(@TempDir Path temp) throws IOException { + Path otherHgDirectory = temp.resolve("other"); + Files.write(configDirectory.resolve("hg.xml"), + asList("", + "", + " false", + " /var/lib/scm/repositories/hg", + " " + otherHgDirectory + "", + " false", + " false", + " UTF-8", + " hg", + " python2", + " ", + " false", + " false", + "" + ) + ); + + Path path = new V1RepositoryMigrationLocationResolver(contextProvider).getTypeDependentPath("hg"); + + assertThat(path).isEqualTo(otherHgDirectory); + } + + @Test + void shouldReturnCustomPathIfAnotherPathIsConfiguredForSvn(@TempDir Path temp) throws IOException { + Path otherSvnDirectory = temp.resolve("other"); + Files.write(configDirectory.resolve("svn.xml"), + asList("", + "", + " false", + " /var/lib/scm/repositories/svn", + " " + otherSvnDirectory + "", + " false", + " NONE", + "" + ) + ); + + Path path = new V1RepositoryMigrationLocationResolver(contextProvider).getTypeDependentPath("svn"); + + assertThat(path).isEqualTo(otherSvnDirectory); + } + + @Test + void shouldReturnDefaultPathForUnknownRepositoryType() { + Path path = new V1RepositoryMigrationLocationResolver(contextProvider).getTypeDependentPath("other"); + + assertThat(path).isEqualTo(scmBaseDirectory.resolve("repositories").resolve("other")); + } +} diff --git a/scm-webapp/src/test/resources/sonia/scm/update/repository/scm-home.v1.zip b/scm-webapp/src/test/resources/sonia/scm/update/repository/scm-home.v1.zip index 4b21785f20..4fe6be6caa 100644 Binary files a/scm-webapp/src/test/resources/sonia/scm/update/repository/scm-home.v1.zip and b/scm-webapp/src/test/resources/sonia/scm/update/repository/scm-home.v1.zip differ