list = null;
+ try {
+ list = Files.list(originalDataDir);
+ } catch (IOException e) {
+ fail("could not read original directory", e);
+ }
+ list.forEach(
+ original -> {
+ Path expectedTarget = targetDataDir.resolve(original.getFileName());
+ assertThat(expectedTarget).exists();
+ if (Files.isDirectory(original)) {
+ assertDirectoriesEqual(expectedTarget, original);
+ } else {
+ assertThat(expectedTarget).hasSameContentAs(original);
+ }
+ }
+ );
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/V1RepositoryFileSystem.java b/scm-webapp/src/test/java/sonia/scm/repository/update/V1RepositoryFileSystem.java
new file mode 100644
index 0000000000..146953d790
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/V1RepositoryFileSystem.java
@@ -0,0 +1,67 @@
+package sonia.scm.repository.update;
+
+import sonia.scm.repository.spi.ZippedRepositoryTestBase;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+class V1RepositoryFileSystem {
+ /**
+ * Creates the following v1 repositories in the temp dir:
+ *
+ *
+ *
+ * arthur@dent.uk
+ * 1558423492071
+ * A repository with two folders.
+ * 3b91caa5-59c3-448f-920b-769aaa56b761
+ * one/directory
+ * false
+ * false
+ * git
+ *
+ *
+ *
+ * arthur@dent.uk
+ * 1558423543716
+ * A repository in deeply nested folders.
+ * c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f
+ * some/more/directories/than/one
+ * false
+ * false
+ * git
+ *
+ *
+ *
+ * arthur@dent.uk
+ * 1558423440258
+ * A simple repository without directories.
+ * 454972da-faf9-4437-b682-dc4a4e0aa8eb
+ * 1558425918578
+ * simple
+ *
+ * true
+ * mice
+ * WRITE
+ *
+ *
+ * false
+ * dent
+ * OWNER
+ *
+ *
+ * false
+ * trillian
+ * READ
+ *
+ * false
+ * false
+ * git
+ * http://localhost:8081/scm/git/simple
+ *
+ *
+ */
+ static void createV1Home(Path tempDir) throws IOException {
+ ZippedRepositoryTestBase.extract(tempDir.toFile(), "sonia/scm/repository/update/scm-home.v1.zip");
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
index c9a64a10e4..9b455b9590 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
@@ -63,64 +63,9 @@ class XmlRepositoryV1UpdateStepTest {
@Nested
class WithExistingDatabase {
- /**
- * Creates the following v1 repositories in the temp dir:
- *
- *
- *
- * arthur@dent.uk
- * 1558423492071
- * A repository with two folders.
- * 3b91caa5-59c3-448f-920b-769aaa56b761
- * one/directory
- * false
- * false
- * git
- *
- *
- *
- * arthur@dent.uk
- * 1558423543716
- * A repository in deeply nested folders.
- * c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f
- * some/more/directories/than/one
- * false
- * false
- * git
- *
- *
- *
- * arthur@dent.uk
- * 1558423440258
- * A simple repository without directories.
- * 454972da-faf9-4437-b682-dc4a4e0aa8eb
- * 1558425918578
- * simple
- *
- * true
- * mice
- * WRITE
- *
- *
- * false
- * dent
- * OWNER
- *
- *
- * false
- * trillian
- * READ
- *
- * false
- * false
- * git
- * http://localhost:8081/scm/git/simple
- *
- *
- */
@BeforeEach
void createV1Home(@TempDirectory.TempDir Path tempDir) throws IOException {
- ZippedRepositoryTestBase.extract(tempDir.toFile(), "sonia/scm/repository/update/scm-home.v1.zip");
+ V1RepositoryFileSystem.createV1Home(tempDir);
}
@BeforeEach
From 3e46aefb4b1b9b9255bcc80f4a856854d9daa4bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Wed, 22 May 2019 17:18:04 +0200
Subject: [PATCH 09/19] Implement move strategy
---
.../update/MoveMigrationStrategy.java | 96 ++++++++++++++++++-
.../update/MoveMigrationStrategyTest.java | 62 ++++++++++++
2 files changed, 156 insertions(+), 2 deletions(-)
create mode 100644 scm-webapp/src/test/java/sonia/scm/repository/update/MoveMigrationStrategyTest.java
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/MoveMigrationStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/update/MoveMigrationStrategy.java
index c2ccb91713..8a8a3b3823 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/MoveMigrationStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/MoveMigrationStrategy.java
@@ -1,21 +1,113 @@
package sonia.scm.repository.update;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
+import sonia.scm.migration.UpdateException;
+import sonia.scm.repository.AbstractSimpleRepositoryHandler;
+import sonia.scm.repository.RepositoryLocationResolver;
import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static java.util.Arrays.asList;
class MoveMigrationStrategy implements MigrationStrategy.Instance {
+ private static final Logger LOG = LoggerFactory.getLogger(MoveMigrationStrategy.class);
+
private final SCMContextProvider contextProvider;
+ private final RepositoryLocationResolver locationResolver;
@Inject
- public MoveMigrationStrategy(SCMContextProvider contextProvider) {
+ public MoveMigrationStrategy(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
this.contextProvider = contextProvider;
+ this.locationResolver = locationResolver;
}
@Override
public Path migrate(String id, String name, String type) {
- return null;
+ Path repositoryBasePath = locationResolver.forClass(Path.class).getLocation(id);
+ Path targetDataPath = repositoryBasePath
+ .resolve(AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
+ Path sourceDataPath = getSourceDataPath(name, type);
+ moveData(sourceDataPath, targetDataPath);
+ deleteOldDataDir(getTypeDependentPath(type), name);
+ return repositoryBasePath;
+ }
+
+ private void deleteOldDataDir(Path rootPath, String name) {
+ delete(rootPath, asList(name.split("/")));
+ }
+
+ private void delete(Path rootPath, List directories) {
+ if (directories.isEmpty()) {
+ return;
+ }
+ Path directory = rootPath.resolve(directories.get(0));
+ delete(directory, directories.subList(1, directories.size()));
+ try {
+ Files.deleteIfExists(directory);
+ } catch (IOException e) {
+ LOG.warn("could not delete source repository directory {}", directory);
+ }
+ }
+
+ private Path getSourceDataPath(String name, String type) {
+ return Arrays.stream(name.split("/"))
+ .reduce(getTypeDependentPath(type), (path, namePart) -> path.resolve(namePart), (p1, p2) -> p1);
+ }
+
+ private Path getTypeDependentPath(String type) {
+ return contextProvider.getBaseDirectory().toPath().resolve("repositories").resolve(type);
+ }
+
+ private void moveData(Path sourceDirectory, Path targetDirectory) {
+ createDataDirectory(targetDirectory);
+ Stream list = listSourceDirectory(sourceDirectory);
+ list.forEach(
+ sourceFile -> {
+ Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
+ if (Files.isDirectory(sourceFile)) {
+ moveData(sourceFile, targetFile);
+ } else {
+ moveFile(sourceFile, targetFile);
+ }
+ }
+ );
+ try {
+ Files.delete(sourceDirectory);
+ } catch (IOException e) {
+ LOG.warn("could not delete source repository directory {}", sourceDirectory);
+ }
+ }
+
+ private Stream listSourceDirectory(Path sourceDirectory) {
+ try {
+ return Files.list(sourceDirectory);
+ } catch (IOException e) {
+ throw new UpdateException("could not read original directory", e);
+ }
+ }
+
+ private void moveFile(Path sourceFile, Path targetFile) {
+ try {
+ Files.move(sourceFile, targetFile);
+ } catch (IOException e) {
+ throw new UpdateException("could not move data file from " + sourceFile + " to " + targetFile, e);
+ }
+ }
+
+ private void createDataDirectory(Path target) {
+ try {
+ Files.createDirectories(target);
+ } catch (IOException e) {
+ throw new UpdateException("could not create data directory " + target, e);
+ }
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/MoveMigrationStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/MoveMigrationStrategyTest.java
new file mode 100644
index 0000000000..ce894516df
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/MoveMigrationStrategyTest.java
@@ -0,0 +1,62 @@
+package sonia.scm.repository.update;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junitpioneer.jupiter.TempDirectory;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import sonia.scm.SCMContextProvider;
+import sonia.scm.repository.RepositoryLocationResolver;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(TempDirectory.class)
+@ExtendWith(MockitoExtension.class)
+class MoveMigrationStrategyTest {
+
+ @Mock
+ SCMContextProvider contextProvider;
+ @Mock
+ RepositoryLocationResolver locationResolver;
+
+ @BeforeEach
+ void mockContextProvider(@TempDirectory.TempDir Path tempDir) {
+ when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
+ }
+
+ @BeforeEach
+ void createV1Home(@TempDirectory.TempDir Path tempDir) throws IOException {
+ V1RepositoryFileSystem.createV1Home(tempDir);
+ }
+
+ @BeforeEach
+ void mockLocationResolver(@TempDirectory.TempDir Path tempDir) {
+ RepositoryLocationResolver.RepositoryLocationResolverInstance instanceMock = mock(RepositoryLocationResolver.RepositoryLocationResolverInstance.class);
+ when(locationResolver.forClass(Path.class)).thenReturn(instanceMock);
+ when(instanceMock.getLocation(anyString())).thenAnswer(invocation -> tempDir.resolve((String) invocation.getArgument(0)));
+ }
+
+ @Test
+ void shouldUseStandardDirectory(@TempDirectory.TempDir Path tempDir) {
+ Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
+ assertThat(target).isEqualTo(tempDir.resolve("b4f-a9f0-49f7-ad1f-37d3aae1c55f"));
+ }
+
+ @Test
+ void shouldMoveDataDirectory(@TempDirectory.TempDir Path tempDir) {
+ Path target = new MoveMigrationStrategy(contextProvider, locationResolver).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
+ assertThat(target.resolve("data")).exists();
+ Path originalDataDir = tempDir
+ .resolve("repositories")
+ .resolve("git")
+ .resolve("some");
+ assertThat(originalDataDir).doesNotExist();
+ }
+}
From 31e56a667213fcbee7d525d6154d203a82b32602 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Thu, 23 May 2019 15:06:40 +0200
Subject: [PATCH 10/19] Implement inline strategy
---
.../update/BaseMigrationStrategy.java | 60 +++++++++++++++++++
.../update/CopyMigrationStrategy.java | 45 +-------------
.../update/InlineMigrationStrategy.java | 31 ++++++++--
.../update/MoveMigrationStrategy.java | 44 +-------------
.../update/InlineMigrationStrategyTest.java | 49 +++++++++++++++
5 files changed, 141 insertions(+), 88 deletions(-)
create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/update/BaseMigrationStrategy.java
create mode 100644 scm-webapp/src/test/java/sonia/scm/repository/update/InlineMigrationStrategyTest.java
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/BaseMigrationStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/update/BaseMigrationStrategy.java
new file mode 100644
index 0000000000..3ac0a6fd68
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/BaseMigrationStrategy.java
@@ -0,0 +1,60 @@
+package sonia.scm.repository.update;
+
+import sonia.scm.SCMContextProvider;
+import sonia.scm.migration.UpdateException;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+abstract class BaseMigrationStrategy implements MigrationStrategy.Instance {
+
+ private final SCMContextProvider contextProvider;
+
+ BaseMigrationStrategy(SCMContextProvider contextProvider) {
+ this.contextProvider = contextProvider;
+ }
+
+ Path getSourceDataPath(String name, String type) {
+ return Arrays.stream(name.split("/"))
+ .reduce(getTypeDependentPath(type), (path, namePart) -> path.resolve(namePart), (p1, p2) -> p1);
+ }
+
+ Path getTypeDependentPath(String type) {
+ return contextProvider.getBaseDirectory().toPath().resolve("repositories").resolve(type);
+ }
+
+ Stream listSourceDirectory(Path sourceDirectory) {
+ try {
+ return Files.list(sourceDirectory);
+ } catch (IOException e) {
+ throw new UpdateException("could not read original directory", e);
+ }
+ }
+
+ void createDataDirectory(Path target) {
+ try {
+ Files.createDirectories(target);
+ } catch (IOException e) {
+ throw new UpdateException("could not create data directory " + target, e);
+ }
+ }
+
+ void moveFile(Path sourceFile, Path targetFile) {
+ try {
+ Files.move(sourceFile, targetFile);
+ } catch (IOException e) {
+ throw new UpdateException("could not move data file from " + sourceFile + " to " + targetFile, e);
+ }
+ }
+
+ void copyFile(Path sourceFile, Path targetFile) {
+ try {
+ Files.copy(sourceFile, targetFile);
+ } catch (IOException e) {
+ throw new UpdateException("could not copy original file from " + sourceFile + " to " + targetFile, e);
+ }
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/CopyMigrationStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/update/CopyMigrationStrategy.java
index 857c3c3428..84835543e0 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/CopyMigrationStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/CopyMigrationStrategy.java
@@ -1,25 +1,20 @@
package sonia.scm.repository.update;
import sonia.scm.SCMContextProvider;
-import sonia.scm.migration.UpdateException;
import sonia.scm.repository.AbstractSimpleRepositoryHandler;
import sonia.scm.repository.RepositoryLocationResolver;
import javax.inject.Inject;
-import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.stream.Stream;
-class CopyMigrationStrategy implements MigrationStrategy.Instance {
+class CopyMigrationStrategy extends BaseMigrationStrategy {
- private final SCMContextProvider contextProvider;
private final RepositoryLocationResolver locationResolver;
@Inject
public CopyMigrationStrategy(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
- this.contextProvider = contextProvider;
+ super(contextProvider);
this.locationResolver = locationResolver;
}
@@ -33,19 +28,9 @@ class CopyMigrationStrategy implements MigrationStrategy.Instance {
return repositoryBasePath;
}
- private Path getSourceDataPath(String name, String type) {
- return Arrays.stream(name.split("/"))
- .reduce(getTypeDependentPath(type), (path, namePart) -> path.resolve(namePart), (p1, p2) -> p1);
- }
-
- private Path getTypeDependentPath(String type) {
- return contextProvider.getBaseDirectory().toPath().resolve("repositories").resolve(type);
- }
-
private void copyData(Path sourceDirectory, Path targetDirectory) {
createDataDirectory(targetDirectory);
- Stream list = listSourceDirectory(sourceDirectory);
- list.forEach(
+ listSourceDirectory(sourceDirectory).forEach(
sourceFile -> {
Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
if (Files.isDirectory(sourceFile)) {
@@ -56,28 +41,4 @@ class CopyMigrationStrategy implements MigrationStrategy.Instance {
}
);
}
-
- private Stream listSourceDirectory(Path sourceDirectory) {
- try {
- return Files.list(sourceDirectory);
- } catch (IOException e) {
- throw new UpdateException("could not read original directory", e);
- }
- }
-
- private void copyFile(Path sourceFile, Path targetFile) {
- try {
- Files.copy(sourceFile, targetFile);
- } catch (IOException e) {
- throw new UpdateException("could not copy original file from " + sourceFile + " to " + targetFile, e);
- }
- }
-
- private void createDataDirectory(Path target) {
- try {
- Files.createDirectories(target);
- } catch (IOException e) {
- throw new UpdateException("could not create data directory " + target, e);
- }
- }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/InlineMigrationStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/update/InlineMigrationStrategy.java
index 1374461bea..dc4fdb067d 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/InlineMigrationStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/InlineMigrationStrategy.java
@@ -1,21 +1,42 @@
package sonia.scm.repository.update;
import sonia.scm.SCMContextProvider;
+import sonia.scm.repository.AbstractSimpleRepositoryHandler;
import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
-class InlineMigrationStrategy implements MigrationStrategy.Instance {
-
- private final SCMContextProvider contextProvider;
+class InlineMigrationStrategy extends BaseMigrationStrategy {
@Inject
public InlineMigrationStrategy(SCMContextProvider contextProvider) {
- this.contextProvider = contextProvider;
+ super(contextProvider);
}
@Override
public Path migrate(String id, String name, String type) {
- return null;
+ Path repositoryBasePath = getSourceDataPath(name, type);
+ Path targetDataPath = repositoryBasePath
+ .resolve(AbstractSimpleRepositoryHandler.REPOSITORIES_NATIVE_DIRECTORY);
+ moveData(repositoryBasePath, targetDataPath);
+ return repositoryBasePath;
+ }
+
+ private void moveData(Path sourceDirectory, Path targetDirectory) {
+ createDataDirectory(targetDirectory);
+ listSourceDirectory(sourceDirectory)
+ .filter(sourceFile -> !targetDirectory.equals(sourceFile))
+ .forEach(
+ sourceFile -> {
+ Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
+ if (Files.isDirectory(sourceFile)) {
+ moveData(sourceFile, targetFile);
+ } else {
+ moveFile(sourceFile, targetFile);
+ }
+ }
+ );
}
}
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/MoveMigrationStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/update/MoveMigrationStrategy.java
index 8a8a3b3823..e0abc7ad9b 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/MoveMigrationStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/MoveMigrationStrategy.java
@@ -3,7 +3,6 @@ package sonia.scm.repository.update;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
-import sonia.scm.migration.UpdateException;
import sonia.scm.repository.AbstractSimpleRepositoryHandler;
import sonia.scm.repository.RepositoryLocationResolver;
@@ -11,22 +10,19 @@ import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Arrays;
import java.util.List;
-import java.util.stream.Stream;
import static java.util.Arrays.asList;
-class MoveMigrationStrategy implements MigrationStrategy.Instance {
+class MoveMigrationStrategy extends BaseMigrationStrategy {
private static final Logger LOG = LoggerFactory.getLogger(MoveMigrationStrategy.class);
- private final SCMContextProvider contextProvider;
private final RepositoryLocationResolver locationResolver;
@Inject
public MoveMigrationStrategy(SCMContextProvider contextProvider, RepositoryLocationResolver locationResolver) {
- this.contextProvider = contextProvider;
+ super(contextProvider);
this.locationResolver = locationResolver;
}
@@ -58,19 +54,9 @@ class MoveMigrationStrategy implements MigrationStrategy.Instance {
}
}
- private Path getSourceDataPath(String name, String type) {
- return Arrays.stream(name.split("/"))
- .reduce(getTypeDependentPath(type), (path, namePart) -> path.resolve(namePart), (p1, p2) -> p1);
- }
-
- private Path getTypeDependentPath(String type) {
- return contextProvider.getBaseDirectory().toPath().resolve("repositories").resolve(type);
- }
-
private void moveData(Path sourceDirectory, Path targetDirectory) {
createDataDirectory(targetDirectory);
- Stream list = listSourceDirectory(sourceDirectory);
- list.forEach(
+ listSourceDirectory(sourceDirectory).forEach(
sourceFile -> {
Path targetFile = targetDirectory.resolve(sourceFile.getFileName());
if (Files.isDirectory(sourceFile)) {
@@ -86,28 +72,4 @@ class MoveMigrationStrategy implements MigrationStrategy.Instance {
LOG.warn("could not delete source repository directory {}", sourceDirectory);
}
}
-
- private Stream listSourceDirectory(Path sourceDirectory) {
- try {
- return Files.list(sourceDirectory);
- } catch (IOException e) {
- throw new UpdateException("could not read original directory", e);
- }
- }
-
- private void moveFile(Path sourceFile, Path targetFile) {
- try {
- Files.move(sourceFile, targetFile);
- } catch (IOException e) {
- throw new UpdateException("could not move data file from " + sourceFile + " to " + targetFile, e);
- }
- }
-
- private void createDataDirectory(Path target) {
- try {
- Files.createDirectories(target);
- } catch (IOException e) {
- throw new UpdateException("could not create data directory " + target, e);
- }
- }
}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/InlineMigrationStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/InlineMigrationStrategyTest.java
new file mode 100644
index 0000000000..ed0d7eeb40
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/InlineMigrationStrategyTest.java
@@ -0,0 +1,49 @@
+package sonia.scm.repository.update;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junitpioneer.jupiter.TempDirectory;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import sonia.scm.SCMContextProvider;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(TempDirectory.class)
+@ExtendWith(MockitoExtension.class)
+class InlineMigrationStrategyTest {
+
+ @Mock
+ SCMContextProvider contextProvider;
+
+ @BeforeEach
+ void mockContextProvider(@TempDirectory.TempDir Path tempDir) {
+ when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
+ }
+
+ @BeforeEach
+ void createV1Home(@TempDirectory.TempDir Path tempDir) throws IOException {
+ V1RepositoryFileSystem.createV1Home(tempDir);
+ }
+
+ @Test
+ void shouldUseExistingDirectory(@TempDirectory.TempDir Path tempDir) {
+ Path target = new InlineMigrationStrategy(contextProvider).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
+ assertThat(target).isEqualTo(resolveOldDirectory(tempDir));
+ }
+
+ @Test
+ void shouldMoveDataDirectory(@TempDirectory.TempDir Path tempDir) {
+ new InlineMigrationStrategy(contextProvider).migrate("b4f-a9f0-49f7-ad1f-37d3aae1c55f", "some/more/directories/than/one", "git");
+ assertThat(resolveOldDirectory(tempDir).resolve("data")).exists();
+ }
+
+ private Path resolveOldDirectory(Path tempDir) {
+ return tempDir.resolve("repositories").resolve("git").resolve("some").resolve("more").resolve("directories").resolve("than").resolve("one");
+ }
+}
From 035abef465b94538632c8e23b5e6382eab9807b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Thu, 23 May 2019 15:25:25 +0200
Subject: [PATCH 11/19] Assert that the existing repositories.xml file contains
v1 data
---
.../update/XmlRepositoryV1UpdateStep.java | 40 +++++++++++++++----
.../update/XmlRepositoryV1UpdateStepTest.java | 12 +++++-
.../update/formerV2RepositoryFile.xml | 4 ++
3 files changed, 48 insertions(+), 8 deletions(-)
create mode 100644 scm-webapp/src/test/resources/sonia/scm/repository/update/formerV2RepositoryFile.xml
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
index d21eb079fa..c1ef200335 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
@@ -24,10 +24,30 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.stream.Collectors;
+import static java.util.Optional.empty;
+import static java.util.Optional.of;
import static sonia.scm.version.Version.parse;
+/**
+ * Migrates SCM-Manager v1 repository data structure to SCM-Manager v2 data structure.
+ * That is:
+ *
+ * The old repositories.xml file is read
+ * For each repository in this database,
+ *
+ * a new entry in the new repository-paths.xml database is written,
+ * the data directory is moved or copied to a SCM v2 consistent directory. How this is done
+ * can be specified by a strategy (@see {@link MigrationStrategy}), that has to be set in
+ * a database file named migration-plan.xml (to create this file, use {@link MigrationStrategyDao}),
+ * and
+ * the new metadata.xml file is created.
+ *
+ *
+ *
+ */
@Extension
public class XmlRepositoryV1UpdateStep implements UpdateStep {
@@ -62,8 +82,9 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
return;
}
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
- V1RepositoryDatabase v1Database = readV1Database(jaxbContext);
- v1Database.repositoryList.repositories.forEach(this::update);
+ readV1Database(jaxbContext).ifPresent(
+ v1Database -> v1Database.repositoryList.repositories.forEach(this::update)
+ );
}
private void update(V1Repository v1Repository) {
@@ -82,7 +103,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
private Path handleDataDirectory(V1Repository v1Repository) {
MigrationStrategy dataMigrationStrategy =
migrationStrategyDao.get(v1Repository.id)
- .orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
+ .orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
return dataMigrationStrategy.from(injector).migrate(v1Repository.id, v1Repository.name, v1Repository.type);
}
@@ -102,12 +123,12 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
private String getNamespace(V1Repository v1Repository) {
String[] nameParts = getNameParts(v1Repository.name);
- return nameParts.length > 1? nameParts[0]: v1Repository.type;
+ return nameParts.length > 1 ? nameParts[0] : v1Repository.type;
}
private String getName(V1Repository v1Repository) {
String[] nameParts = getNameParts(v1Repository.name);
- return nameParts.length == 1? nameParts[0]: concatPathElements(nameParts);
+ return nameParts.length == 1 ? nameParts[0] : concatPathElements(nameParts);
}
private String concatPathElements(String[] nameParts) {
@@ -118,8 +139,13 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
return v1Name.split("/");
}
- private V1RepositoryDatabase readV1Database(JAXBContext jaxbContext) throws JAXBException {
- return (V1RepositoryDatabase) jaxbContext.createUnmarshaller().unmarshal(determineV1File());
+ private Optional readV1Database(JAXBContext jaxbContext) throws JAXBException {
+ Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(determineV1File());
+ if (unmarshal instanceof V1RepositoryDatabase) {
+ return of((V1RepositoryDatabase) unmarshal);
+ } else {
+ return empty();
+ }
}
private File determineV1File() {
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
index 9b455b9590..3b0008f3c6 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
@@ -1,5 +1,6 @@
package sonia.scm.repository.update;
+import com.google.common.io.Resources;
import com.google.inject.Injector;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -14,11 +15,12 @@ import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermission;
-import sonia.scm.repository.spi.ZippedRepositoryTestBase;
import sonia.scm.repository.xml.XmlRepositoryDAO;
import javax.xml.bind.JAXBException;
import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
@@ -165,7 +167,15 @@ class XmlRepositoryV1UpdateStepTest {
@Test
void shouldNotFailIfNoOldDatabaseExists() throws JAXBException {
updateStep.doUpdate();
+ }
+ @Test
+ void shouldNotFailIfFormerV1DatabaseExists(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException {
+ URL url = Resources.getResource("sonia/scm/repository/update/formerV2RepositoryFile.xml");
+ Path configDir = tempDir.resolve("config");
+ Files.createDirectories(configDir);
+ Files.copy(url.openStream(), configDir.resolve("repositories.xml"));
+ updateStep.doUpdate();
}
private Optional findByNamespace(String namespace) {
diff --git a/scm-webapp/src/test/resources/sonia/scm/repository/update/formerV2RepositoryFile.xml b/scm-webapp/src/test/resources/sonia/scm/repository/update/formerV2RepositoryFile.xml
new file mode 100644
index 0000000000..ca21ead549
--- /dev/null
+++ b/scm-webapp/src/test/resources/sonia/scm/repository/update/formerV2RepositoryFile.xml
@@ -0,0 +1,4 @@
+
+
+ repositories/C2RRHjjeL2
+
From 0a0fd174489656b98d626e45de8beb6b195c06ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Thu, 23 May 2019 17:37:58 +0200
Subject: [PATCH 12/19] Verify that migration strategies are set before
migration starts
---
.../update/XmlRepositoryV1UpdateStep.java | 14 ++++++++++----
.../update/XmlRepositoryV1UpdateStepTest.java | 10 +++++++++-
2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
index c1ef200335..a08f26768c 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
@@ -83,7 +83,10 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
}
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
readV1Database(jaxbContext).ifPresent(
- v1Database -> v1Database.repositoryList.repositories.forEach(this::update)
+ v1Database -> {
+ v1Database.repositoryList.repositories.forEach(this::readMigrationStrategy);
+ v1Database.repositoryList.repositories.forEach(this::update);
+ }
);
}
@@ -101,12 +104,15 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
}
private Path handleDataDirectory(V1Repository v1Repository) {
- MigrationStrategy dataMigrationStrategy =
- migrationStrategyDao.get(v1Repository.id)
- .orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
+ MigrationStrategy dataMigrationStrategy = readMigrationStrategy(v1Repository);
return dataMigrationStrategy.from(injector).migrate(v1Repository.id, v1Repository.name, v1Repository.type);
}
+ private MigrationStrategy readMigrationStrategy(V1Repository v1Repository) {
+ return migrationStrategyDao.get(v1Repository.id)
+ .orElseThrow(() -> new IllegalStateException("no strategy found for repository with id " + v1Repository.id + " and name " + v1Repository.name));
+ }
+
private RepositoryPermission[] createPermissions(V1Repository v1Repository) {
if (v1Repository.permissions == null) {
return new RepositoryPermission[0];
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
index 3b0008f3c6..b7ad55a889 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
@@ -24,8 +24,10 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
+import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.lenient;
@@ -72,7 +74,7 @@ class XmlRepositoryV1UpdateStepTest {
@BeforeEach
void captureStoredRepositories() {
- doNothing().when(repositoryDAO).add(storeCaptor.capture(), locationCaptor.capture());
+ lenient().doNothing().when(repositoryDAO).add(storeCaptor.capture(), locationCaptor.capture());
}
@BeforeEach
@@ -162,6 +164,12 @@ class XmlRepositoryV1UpdateStepTest {
assertThat(locationCaptor.getAllValues()).contains(targetDir);
}
+
+ @Test
+ void shouldFailForMissingMigrationStrategy() throws JAXBException {
+ lenient().when(migrationStrategyDao.get("c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f")).thenReturn(empty());
+ assertThrows(IllegalStateException.class, () -> updateStep.doUpdate());
+ }
}
@Test
From d307ce6df0cdada95611ffbb0bc2b3818023d4e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Fri, 24 May 2019 11:55:04 +0200
Subject: [PATCH 13/19] Set version and add logging
---
.../scm/repository/update/XmlRepositoryV1UpdateStep.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
index a08f26768c..8c93b21822 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
@@ -68,7 +68,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
@Override
public Version getTargetVersion() {
- return parse("0.0.1");
+ return parse("2.0.0");
}
@Override
@@ -79,6 +79,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
@Override
public void doUpdate() throws JAXBException {
if (!determineV1File().exists()) {
+ LOG.info("no v1 repositories database file found");
return;
}
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
@@ -100,6 +101,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
v1Repository.contact,
v1Repository.description,
createPermissions(v1Repository));
+ LOG.info("creating new repository {} with id {} from old repository {} in directory {}", repository.getNamespaceAndName(), repository.getId(), v1Repository.name, destination);
repositoryDao.add(repository, destination);
}
@@ -124,6 +126,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
}
private RepositoryPermission createPermission(V1Permission v1Permission) {
+ LOG.info("creating permission {} for {}", v1Permission.type, v1Permission.name);
return new RepositoryPermission(v1Permission.name, v1Permission.type, v1Permission.groupPermission);
}
From d88b300ac8dedf7fef1e1ee68c5804cf0cd876f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Fri, 24 May 2019 11:55:32 +0200
Subject: [PATCH 14/19] Add update step to rename repositories.xml ->
repository-paths.xml
---
.../PathBasedRepositoryLocationResolver.java | 2 +-
.../XmlRepositoryFileNameUpdateStep.java | 51 +++++++++++++++++++
.../XmlRepositoryFileNameUpdateStepTest.java | 44 ++++++++++++++++
3 files changed, 96 insertions(+), 1 deletion(-)
create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStep.java
create mode 100644 scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStepTest.java
diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathBasedRepositoryLocationResolver.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathBasedRepositoryLocationResolver.java
index 91c653a31d..a1ce516069 100644
--- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathBasedRepositoryLocationResolver.java
+++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/PathBasedRepositoryLocationResolver.java
@@ -30,7 +30,7 @@ import static sonia.scm.ContextEntry.ContextBuilder.entity;
*/
public class PathBasedRepositoryLocationResolver extends BasicRepositoryLocationResolver {
- private static final String STORE_NAME = "repository-paths";
+ public static final String STORE_NAME = "repository-paths";
private final SCMContextProvider contextProvider;
private final InitialRepositoryLocationResolver initialRepositoryLocationResolver;
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStep.java
new file mode 100644
index 0000000000..aa6641f257
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStep.java
@@ -0,0 +1,51 @@
+package sonia.scm.repository.update;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sonia.scm.SCMContextProvider;
+import sonia.scm.migration.UpdateStep;
+import sonia.scm.plugin.Extension;
+import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
+import sonia.scm.store.StoreConstants;
+import sonia.scm.version.Version;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static sonia.scm.version.Version.parse;
+
+@Extension
+public class XmlRepositoryFileNameUpdateStep implements UpdateStep {
+
+ private static final Logger LOG = LoggerFactory.getLogger(XmlRepositoryFileNameUpdateStep.class);
+
+ private final SCMContextProvider contextProvider;
+
+ @Inject
+ public XmlRepositoryFileNameUpdateStep(SCMContextProvider contextProvider) {
+ this.contextProvider = contextProvider;
+ }
+
+ @Override
+ public void doUpdate() throws IOException {
+ Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
+ Path oldRepositoriesFile = configDir.resolve("repositories.xml");
+ Path newRepositoryPathsFile = configDir.resolve(PathBasedRepositoryLocationResolver.STORE_NAME + StoreConstants.FILE_EXTENSION);
+ if (Files.exists(oldRepositoriesFile)) {
+ LOG.info("moving old repositories database files to repository-paths file");
+ Files.move(oldRepositoriesFile, newRepositoryPathsFile);
+ }
+ }
+
+ @Override
+ public Version getTargetVersion() {
+ return parse("2.0.1");
+ }
+
+ @Override
+ public String getAffectedDataType() {
+ return "sonia.scm.repository.xml";
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStepTest.java
new file mode 100644
index 0000000000..6314e02783
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStepTest.java
@@ -0,0 +1,44 @@
+package sonia.scm.repository.update;
+
+import com.google.common.io.Resources;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junitpioneer.jupiter.TempDirectory;
+import sonia.scm.SCMContextProvider;
+import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
+
+import javax.xml.bind.JAXBException;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(TempDirectory.class)
+class XmlRepositoryFileNameUpdateStepTest {
+
+ SCMContextProvider contextProvider = mock(SCMContextProvider.class);
+
+ @BeforeEach
+ void mockScmHome(@TempDirectory.TempDir Path tempDir) {
+ when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
+ }
+
+ @Test
+ void shouldCopyRepositoriesFileToRepositoryPathsFile(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException {
+ XmlRepositoryFileNameUpdateStep updateStep = new XmlRepositoryFileNameUpdateStep(contextProvider);
+ URL url = Resources.getResource("sonia/scm/repository/update/formerV2RepositoryFile.xml");
+ Path configDir = tempDir.resolve("config");
+ Files.createDirectories(configDir);
+ Files.copy(url.openStream(), configDir.resolve("repositories.xml"));
+
+ updateStep.doUpdate();
+
+ assertThat(configDir.resolve(PathBasedRepositoryLocationResolver.STORE_NAME + ".xml")).exists();
+ assertThat(configDir.resolve("repositories.xml")).doesNotExist();
+ }
+}
From 76c59a6dee383369d3f4166a0c82683f4e1c00f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Fri, 24 May 2019 13:55:37 +0200
Subject: [PATCH 15/19] Extract properties from v1 repositories
---
.../update/XmlRepositoryV1UpdateStep.java | 32 ++++++++++++++++--
.../update/XmlRepositoryV1UpdateStepTest.java | 30 ++++++++++++++--
.../scm/repository/update/scm-home.v1.zip | Bin 13593 -> 13412 bytes
3 files changed, 56 insertions(+), 6 deletions(-)
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
index 8c93b21822..9f79287a19 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
@@ -9,6 +9,8 @@ import sonia.scm.plugin.Extension;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.xml.XmlRepositoryDAO;
+import sonia.scm.store.ConfigurationEntryStore;
+import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.StoreConstants;
import sonia.scm.version.Version;
@@ -57,13 +59,24 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
private final XmlRepositoryDAO repositoryDao;
private final MigrationStrategyDao migrationStrategyDao;
private final Injector injector;
+ private final ConfigurationEntryStore propertyStore;
@Inject
- public XmlRepositoryV1UpdateStep(SCMContextProvider contextProvider, XmlRepositoryDAO repositoryDao, MigrationStrategyDao migrationStrategyDao, Injector injector) {
+ public XmlRepositoryV1UpdateStep(
+ SCMContextProvider contextProvider,
+ XmlRepositoryDAO repositoryDao,
+ MigrationStrategyDao migrationStrategyDao,
+ Injector injector,
+ ConfigurationEntryStoreFactory configurationEntryStoreFactory
+ ) {
this.contextProvider = contextProvider;
this.repositoryDao = repositoryDao;
this.migrationStrategyDao = migrationStrategyDao;
this.injector = injector;
+ this.propertyStore = configurationEntryStoreFactory
+ .withType(V1Properties.class)
+ .withName("repository-properties-v1")
+ .build();
}
@Override
@@ -103,6 +116,7 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
createPermissions(v1Repository));
LOG.info("creating new repository {} with id {} from old repository {} in directory {}", repository.getNamespaceAndName(), repository.getId(), v1Repository.name, destination);
repositoryDao.add(repository, destination);
+ propertyStore.put(v1Repository.id, v1Repository.properties);
}
private Path handleDataDirectory(V1Repository v1Repository) {
@@ -170,10 +184,22 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
private String type;
}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ private static class V1Property {
+ private String key;
+ private String value;
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlRootElement(name = "properties")
+ private static class V1Properties {
+ @XmlElement(name = "item")
+ private List properties;
+ }
+
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "repositories")
private static class V1Repository {
- private Map properties;
private String contact;
private long creationDate;
private Long lastModified;
@@ -184,11 +210,11 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
private boolean archived;
private String type;
private List permissions;
+ private V1Properties properties;
@Override
public String toString() {
return "V1Repository{" +
- "properties=" + properties +
", contact='" + contact + '\'' +
", creationDate=" + creationDate +
", lastModified=" + lastModified +
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
index b7ad55a889..8c0f4bb103 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
@@ -16,6 +16,10 @@ import sonia.scm.SCMContextProvider;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.xml.XmlRepositoryDAO;
+import sonia.scm.store.ConfigurationEntryStore;
+import sonia.scm.store.ConfigurationEntryStoreFactory;
+import sonia.scm.store.InMemoryConfigurationEntryStore;
+import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
import javax.xml.bind.JAXBException;
import java.io.IOException;
@@ -29,7 +33,6 @@ import static java.util.Optional.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -48,15 +51,16 @@ class XmlRepositoryV1UpdateStepTest {
SCMContextProvider contextProvider;
@Mock
XmlRepositoryDAO repositoryDAO;
- @Mock()
+ @Mock
MigrationStrategyDao migrationStrategyDao;
+ ConfigurationEntryStoreFactory configurationEntryStoreFactory = new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
+
@Captor
ArgumentCaptor storeCaptor;
@Captor
ArgumentCaptor locationCaptor;
- @InjectMocks
XmlRepositoryV1UpdateStep updateStep;
@BeforeEach
@@ -64,6 +68,17 @@ class XmlRepositoryV1UpdateStepTest {
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
}
+ @BeforeEach
+ void createUpdateStepFromMocks() {
+ updateStep = new XmlRepositoryV1UpdateStep(
+ contextProvider,
+ repositoryDAO,
+ migrationStrategyDao,
+ injectorMock,
+ configurationEntryStoreFactory
+ );
+ }
+
@Nested
class WithExistingDatabase {
@@ -154,6 +169,15 @@ class XmlRepositoryV1UpdateStepTest {
);
}
+ @Test
+ void shouldExtractPropertiesFromRepositories() throws JAXBException {
+ updateStep.doUpdate();
+
+ ConfigurationEntryStore store = configurationEntryStoreFactory.withType(null).withName("").build();
+ assertThat(store.getAll())
+ .hasSize(3);
+ }
+
@Test
void shouldUseDirectoryFromStrategy(@TempDirectory.TempDir Path tempDir) throws JAXBException {
Path targetDir = tempDir.resolve("someDir");
diff --git a/scm-webapp/src/test/resources/sonia/scm/repository/update/scm-home.v1.zip b/scm-webapp/src/test/resources/sonia/scm/repository/update/scm-home.v1.zip
index 5d936db5e264ac6182855e34a5aae82871c2c3fd..0d43f2f4d6a0b5f595e58cda18933e1565bbc083 100644
GIT binary patch
delta 2435
zcmZ8h3s@6Z7M|P?5=fW?0to~X5&{9jD`0q8(HP|+5F!tu7F@7J5eq1a3c{9Jw_87@
zUEp*}YiSJy*P`G9Dk80qszqJ863aud6uPYyq>ADy_EV9a2}y(|-^~2~fBtjsx%1sK
zpKQFf!D3|`)dB*9j#Uj#)beYiRp)!j2h>o0#re7I3ESREU)GK=>^>9rAh+_f`q7Mj
z;@Vcs#3&YHR17oR`cgHU>J^+bPttU?HI9q}4hPd4*W|LF)g3+}N-b4Pt!xrhv@tJ<;~V>Hb=x;cZ;Xa4EjFgcE?@E()Hn~
zPhqezOI5n_8Y)*m%wvpe7O$bG!1;Z6y4P}t)=KGQ5zzrmZ9j+9+y{vs_)
zZrT`@_oL@)`4Ru@8ygzcmAn42;#OiFzdfd6ck7jF7YACsCr_~P&cItze4>^;!n6y;
zzbL+2#(F<FE4X^l;u;B
zy78x4w@b8dzp;-!(3x=8EmEG{qECLd{rfaWcWLnJ0{Nr(ve2PrPZZsPrmoBDI}$J0
zZ=0N~pnso!<*ZL4WjtskV)YYgBr`7!6Qpg{DZVVcwrhfaGVDh59>w&7_=wE4{`B>-
zj{n~O=}=Rfm+InZs!c34;?Z9`QVJ@UpUxZz^LF`ByZ1_GuixF(%X1ue$GxKq%a)(}
zqpu}4J^r_&wsOghnk_rJ?%3V9GSpjM`Wo0-Kkn>2>D?!~x~X*HUT$T?|29j1IPile
zYUYy{Ot7Q{8_xV<+FkP5>Sy^nh04rrhibZ)t5U2mYUQC{Gf2^3uyG***6_KNmBC1Ay;i|SECRW8gAu2h?Nhh_cKzo>GP8!0KtQ50ti;cpJyKMptn1aVs^+cZ(Tn;;NUo-B(1C4Px^l_z-
z!seR!hwMd0EP^|pILxqT+aqh}IZCr{v4aD=?Sn&(H`|)n@g?JqNt-FuVap@!WXGen
zGv-V#_vPw^+)%UL#|<&yrF;?SommiOFP+Et*n7?6dJfS6pEvu)pBUFSB`fL8U(pI01Ax!!x{PFx8plX^g&ybNnpGa*UkES2!&K+zJ1a7E5ws|E-6e
zB$RnEoTmhii}ASpx%nLDm^tR;*&=dS$Pk*~N|#q4%ZdR-41a^OP$Y(Mp@4!xiZB@s
zV9+RBAfErp#xOqSh><@EaW(83qrh{ah)R6Kp-|)k;UW>5w1Cy3B+4)}i9#0=-w=ClgA;gogw1x6OEc@;Rik%
zWXmjJ3*1>3Y
zC8jDGot{g{A&W+Syg3dUR0e3!A(P|*e1!`6^39HYiWpS;lK$0+0qj6E!X3>HQo^0+
z@L`||y*pQC13yTGp1+j#1;wCmoy>`+1x!%-1wn#8j+6qkOw1+hTcAeu^Cg`#v5ZoM
z9G&MlS@3}}h{G|?28MC4drK>vp~W=>jw?l|-x>7E;A*)A=p~jg<|;zoF2E4`+0!hE
Qk;1}>|4lxI6}gb?|49vj^Z)<=
delta 2169
zcmY+EeN+=y8pe~0A(MoJ2_YmILikKf2qZvKC~7IjN>WrLNCn%1vXn~D0!ohvEFf-S
zYqwb8^<#_eB1oz0fr_xuqNqhF?QWHirCV()7Nynd8kOp)daSxLlQ4V8Klk@M?{n|W
z+`RYxl>dGK;5Gwo&bkz_SY22n3PA}+p~hQU?OdHz
zv_HzX^7fI$vQp)CUaI|Ava{{En@jBN32Qe(rb+%?d)AZ3%6*!ekFrdT?^VN+lh-cX
zfAF^rOR659*gNQPKIzca0-u!iuq`e?6ChNc2
z8FM=P#L|uk1G@iHdP!H|hkFi{iHA2;CttPi-nB7dbcR*Yx-9CqgI}lfM#{@SsC9hM
z7Oz^j@?K@q;JH=#@=L1is{G!9{=S{{R_vvEPJv)-%f$84cjLBvUs*ytdhqt7{uJl<
zk9&TYh~Rl2|MfyKF@vw7TfgQHvK<(yi6_elX)XY->X0T&7QQeR^-^a^U?S&
z)Kob1r@JllonJ0#n(Q0M6ztD^x}_Lv?*8TW13%F6WT&p}&qu>$`cD_1ssH(A=accM
zk;cY!th=Djwpxv4X0~W^#4K-zGQ;?AeR}n|O`Z1!zsOp1)04Q_@wh1eg|9ZMK3*>`
zcwsU*A>qBlJ%jez>O9?!7x$i39N-^58}Lu<;-5o43{B$U2_CkxveW;*YZ$Q4-#Ynm
zz_nw3ub0;B556+qyE^Mp-b!7^t&1_!zl`4fW9!Rf5dYkn(k~wWdgeyq#bDL2@y3Ex
zn|}PLX!nZ)8(Pn+t4}_C^V8q=g}oBl<~ufEs3I)mUg;i1u#MNo
z)b0F8n(y!vV3aR%^Ky5cIvcP?@LwigiYcMq%N3&@^_5o?QyU>`i-i(ekBJDV5r#mm
zSOOG}2_qQRiJ}?SOG05v?FCh0G9y~;_C;Xz?q)I?+{~~O*Mm^Yb>|6i;_-CNb&O$&
z_#qQ@_(a3eFe!9-Veq*ShcA8N=4ibnme#mj4m-)$pmur6mw-zWEyEgI3U?HKknPK3
zJSXk>L#jea<}(;Sg)oC5k_*jJF5^u|tqfcJR=AnmKEH1mzfZQ5;b!?FPH|4|2BUj*
z0-EK?bd~(TZbdMgNv04MhEEVSh9{(E2r_v=nu2im%rb}yCgfa}oaOrAEazg1TsI|L
z4pc*m2@e#`2~deSS0;2RweXgbV80E`%2j9`8}2DhL31xj7E9({giTIwt0_ic4Ny_{
zrdTC|;t)@Gi`EZ4l$7W?3Qu68G
zCqYSQhYRsw8@lYmH~4(03Qj}|1$5{QK1Lo4E?g(ArvF<80TM
zsB%_%L!CAeO}pfEt&P184(LK4+(!Uwb&2eUq;jAP2{~Bq4E;#!iAe#)Z&nw6H%TX*)71$kYP(B+81~DU1PK
zw2i$1W};2tO$eYXI+1;eROM)eKV1$okqsGRL#JnD0
Date: Fri, 24 May 2019 14:25:35 +0200
Subject: [PATCH 16/19] Backup old repositories file
---
.../update/XmlRepositoryV1UpdateStep.java | 17 ++++++++++-
.../update/XmlRepositoryV1UpdateStepTest.java | 30 ++++++++++++++++---
2 files changed, 42 insertions(+), 5 deletions(-)
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
index 9f79287a19..813d42818f 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStep.java
@@ -4,6 +4,7 @@ import com.google.inject.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
+import sonia.scm.migration.UpdateException;
import sonia.scm.migration.UpdateStep;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.Repository;
@@ -22,10 +23,11 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -100,10 +102,23 @@ public class XmlRepositoryV1UpdateStep implements UpdateStep {
v1Database -> {
v1Database.repositoryList.repositories.forEach(this::readMigrationStrategy);
v1Database.repositoryList.repositories.forEach(this::update);
+ backupOldRepositoriesFile();
}
);
}
+ private void backupOldRepositoriesFile() {
+ Path configDir = contextProvider.getBaseDirectory().toPath().resolve(StoreConstants.CONFIG_DIRECTORY_NAME);
+ Path oldRepositoriesFile = configDir.resolve("repositories.xml");
+ Path backupFile = configDir.resolve("repositories.xml.v1.backup");
+ LOG.info("moving old repositories database files to backup file {}", backupFile);
+ try {
+ Files.move(oldRepositoriesFile, backupFile);
+ } catch (IOException e) {
+ throw new UpdateException("could not backup old repository database file", e);
+ }
+ }
+
private void update(V1Repository v1Repository) {
Path destination = handleDataDirectory(v1Repository);
Repository repository = new Repository(
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
index 8c0f4bb103..773d7bd447 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/update/XmlRepositoryV1UpdateStepTest.java
@@ -9,7 +9,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
@@ -190,10 +189,18 @@ class XmlRepositoryV1UpdateStepTest {
}
@Test
- void shouldFailForMissingMigrationStrategy() throws JAXBException {
+ void shouldFailForMissingMigrationStrategy() {
lenient().when(migrationStrategyDao.get("c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f")).thenReturn(empty());
assertThrows(IllegalStateException.class, () -> updateStep.doUpdate());
}
+
+ @Test
+ void shouldBackupOldRepositoryDatabaseFile(@TempDirectory.TempDir Path tempDir) throws JAXBException {
+ updateStep.doUpdate();
+
+ assertThat(tempDir.resolve("config").resolve("repositories.xml")).doesNotExist();
+ assertThat(tempDir.resolve("config").resolve("repositories.xml.v1.backup")).exists();
+ }
}
@Test
@@ -202,12 +209,27 @@ class XmlRepositoryV1UpdateStepTest {
}
@Test
- void shouldNotFailIfFormerV1DatabaseExists(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException {
+ void shouldNotFailIfFormerV2DatabaseExists(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException {
+ createFormerV2RepositoriesFile(tempDir);
+
+ updateStep.doUpdate();
+ }
+
+ @Test
+ void shouldNotBackupFormerV2DatabaseFile(@TempDirectory.TempDir Path tempDir) throws JAXBException, IOException {
+ createFormerV2RepositoriesFile(tempDir);
+
+ updateStep.doUpdate();
+
+ assertThat(tempDir.resolve("config").resolve("repositories.xml")).exists();
+ assertThat(tempDir.resolve("config").resolve("repositories.xml.v1.backup")).doesNotExist();
+ }
+
+ private void createFormerV2RepositoriesFile(@TempDirectory.TempDir Path tempDir) throws IOException {
URL url = Resources.getResource("sonia/scm/repository/update/formerV2RepositoryFile.xml");
Path configDir = tempDir.resolve("config");
Files.createDirectories(configDir);
Files.copy(url.openStream(), configDir.resolve("repositories.xml"));
- updateStep.doUpdate();
}
private Optional findByNamespace(String namespace) {
From e8c37f3a5016859cff387042e24981e5eb6773e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Tue, 28 May 2019 08:12:31 +0200
Subject: [PATCH 17/19] Add doc
---
.../repository/update/XmlRepositoryFileNameUpdateStep.java | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStep.java
index aa6641f257..765f3c6317 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/update/XmlRepositoryFileNameUpdateStep.java
@@ -16,6 +16,11 @@ import java.nio.file.Path;
import static sonia.scm.version.Version.parse;
+/**
+ * Moves an existing repositories.xml file to repository-paths.xml.
+ * Note that this has to run after an old v1 repository database has been migrated to v2
+ * (see {@link XmlRepositoryV1UpdateStep}).
+ */
@Extension
public class XmlRepositoryFileNameUpdateStep implements UpdateStep {
From 62447139700a86bb61a7b9bedfb276c519a74957 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Mon, 3 Jun 2019 13:47:05 +0200
Subject: [PATCH 18/19] Move constant to interface
---
.../scm/repository/AbstractSimpleRepositoryHandler.java | 3 ---
.../sonia/scm/repository/RepositoryDirectoryHandler.java | 2 ++
.../java/sonia/scm/repository/GitRepositoryHandlerTest.java | 2 +-
.../java/sonia/scm/repository/HgRepositoryHandlerTest.java | 2 +-
.../java/sonia/scm/repository/SvnRepositoryHandlerTest.java | 2 +-
.../scm/repository/SimpleRepositoryHandlerTestBase.java | 2 +-
.../sonia/scm/repository/update/CopyMigrationStrategy.java | 4 ++--
.../sonia/scm/repository/update/InlineMigrationStrategy.java | 5 ++---
.../sonia/scm/repository/update/MoveMigrationStrategy.java | 4 ++--
9 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java
index 1335001cdc..62dfd476af 100644
--- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java
+++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java
@@ -59,9 +59,6 @@ public abstract class AbstractSimpleRepositoryHandler
Date: Mon, 3 Jun 2019 12:49:03 +0000
Subject: [PATCH 19/19] Close branch feature/migrate_repository_v1