mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-07 14:33:16 +02:00
Feature repository specific data migration (#1526)
This adds a new migration mechanism for repository data. Instead of using UpdateSteps for all data migrations, repository data shall from now on be implemented with RepositoryUpdateSteps. The general logic stays the same. Executed updates are stored with the repository. Doing this, we can now execute updates on imported repositories without touching other data. This way we can import repositories even though they were exported with older versions of SCM-Manager or a plugin.
This commit is contained in:
@@ -27,6 +27,7 @@ package sonia.scm.importexport;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.io.Resources;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -39,9 +40,11 @@ import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.repository.RepositoryTestData;
|
||||
import sonia.scm.repository.api.ImportFailedException;
|
||||
import sonia.scm.repository.api.IncompatibleEnvironmentForImportException;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.repository.api.UnbundleCommandBuilder;
|
||||
import sonia.scm.update.UpdateEngine;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -49,16 +52,19 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
class FullScmRepositoryImporterTest {
|
||||
|
||||
private static final Repository REPOSITORY = RepositoryTestData.createHeartOfGold("svn");
|
||||
@@ -75,6 +81,8 @@ class FullScmRepositoryImporterTest {
|
||||
private ScmEnvironmentCompatibilityChecker compatibilityChecker;
|
||||
@Mock
|
||||
private TarArchiveRepositoryStoreImporter storeImporter;
|
||||
@Mock
|
||||
private UpdateEngine updateEngine;
|
||||
|
||||
@InjectMocks
|
||||
private FullScmRepositoryImporter fullImporter;
|
||||
@@ -89,29 +97,57 @@ class FullScmRepositoryImporterTest {
|
||||
void shouldNotImportRepositoryIfFileNotExists(@TempDir Path temp) throws IOException {
|
||||
File emptyFile = new File(temp.resolve("empty").toString());
|
||||
Files.touch(emptyFile);
|
||||
assertThrows(ImportFailedException.class, () -> fullImporter.importFromStream(REPOSITORY, new FileInputStream(emptyFile)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfScmEnvironmentIsIncompatible() {
|
||||
when(compatibilityChecker.check(any())).thenReturn(false);
|
||||
|
||||
FileInputStream inputStream = new FileInputStream(emptyFile);
|
||||
assertThrows(
|
||||
ImportFailedException.class,
|
||||
() -> fullImporter.importFromStream(REPOSITORY, Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream())
|
||||
() -> fullImporter.importFromStream(REPOSITORY, inputStream)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldImportScmRepositoryArchive() throws IOException {
|
||||
when(compatibilityChecker.check(any())).thenReturn(true);
|
||||
when(repositoryManager.create(eq(REPOSITORY), any())).thenReturn(REPOSITORY);
|
||||
void shouldFailIfScmEnvironmentIsIncompatible() throws IOException {
|
||||
when(compatibilityChecker.check(any())).thenReturn(false);
|
||||
|
||||
Repository repository = fullImporter.importFromStream(REPOSITORY, Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream());
|
||||
assertThat(repository).isEqualTo(REPOSITORY);
|
||||
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class));
|
||||
verify(repositoryManager).modify(REPOSITORY);
|
||||
Collection<RepositoryPermission> updatedPermissions = REPOSITORY.getPermissions();
|
||||
assertThat(updatedPermissions).hasSize(2);
|
||||
InputStream importStream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
|
||||
assertThrows(
|
||||
IncompatibleEnvironmentForImportException.class,
|
||||
() -> fullImporter.importFromStream(REPOSITORY, importStream)
|
||||
);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithValidEnvironment {
|
||||
|
||||
@BeforeEach
|
||||
void setUpEnvironment() {
|
||||
when(compatibilityChecker.check(any())).thenReturn(true);
|
||||
when(repositoryManager.create(eq(REPOSITORY), any())).thenAnswer(invocation -> {
|
||||
invocation.getArgument(1, Consumer.class).accept(REPOSITORY);
|
||||
return REPOSITORY;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldImportScmRepositoryArchive() throws IOException {
|
||||
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
|
||||
|
||||
Repository repository = fullImporter.importFromStream(REPOSITORY, stream);
|
||||
|
||||
assertThat(repository).isEqualTo(REPOSITORY);
|
||||
verify(storeImporter).importFromTarArchive(eq(REPOSITORY), any(InputStream.class));
|
||||
verify(repositoryManager).modify(REPOSITORY);
|
||||
Collection<RepositoryPermission> updatedPermissions = REPOSITORY.getPermissions();
|
||||
assertThat(updatedPermissions).hasSize(2);
|
||||
verify(unbundleCommandBuilder).unbundle((InputStream) argThat(argument -> argument.getClass().equals(FullScmRepositoryImporter.NoneClosingInputStream.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTriggerUpdateForImportedRepository() throws IOException {
|
||||
InputStream stream = Resources.getResource("sonia/scm/repository/import/scm-import.tar.gz").openStream();
|
||||
|
||||
fullImporter.importFromStream(REPOSITORY, stream);
|
||||
|
||||
verify(updateEngine).update(REPOSITORY.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
import sonia.scm.version.Version;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -61,7 +62,7 @@ class ScmEnvironmentCompatibilityCheckerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrueIfEnvironmentIsCompatible() {
|
||||
void shouldReturnTrueIfEnvironmentIsSame() {
|
||||
when(scmContextProvider.getVersion()).thenReturn("2.0.0");
|
||||
ImmutableList<EnvironmentPluginDescriptor> plugins = ImmutableList.of(
|
||||
new EnvironmentPluginDescriptor("scm-first-plugin", "1.0.0"),
|
||||
@@ -74,6 +75,20 @@ class ScmEnvironmentCompatibilityCheckerTest {
|
||||
assertThat(compatible).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrueIfEnvironmentIsNewer() {
|
||||
when(scmContextProvider.getVersion()).thenReturn("2.1.0");
|
||||
ImmutableList<EnvironmentPluginDescriptor> plugins = ImmutableList.of(
|
||||
new EnvironmentPluginDescriptor("scm-first-plugin", "0.9.0"),
|
||||
new EnvironmentPluginDescriptor("scm-second-plugin", "1.0.1")
|
||||
);
|
||||
ScmEnvironment env = createScmEnvironment("2.0.0", "linux", "64", plugins);
|
||||
|
||||
boolean compatible = checker.check(env);
|
||||
|
||||
assertThat(compatible).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalseIfCoreVersionIncompatible() {
|
||||
when(scmContextProvider.getVersion()).thenReturn("2.0.0");
|
||||
@@ -124,5 +139,4 @@ class ScmEnvironmentCompatibilityCheckerTest {
|
||||
scmEnvironment.setPlugins(environmentPluginsDescriptor);
|
||||
return scmEnvironment;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
|
||||
package sonia.scm.update;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sonia.scm.migration.RepositoryUpdateContext;
|
||||
import sonia.scm.migration.RepositoryUpdateStep;
|
||||
import sonia.scm.migration.UpdateStep;
|
||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||
import sonia.scm.store.InMemoryConfigurationEntryStoreFactory;
|
||||
@@ -33,16 +36,35 @@ import sonia.scm.version.Version;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.singleton;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
class UpdateEngineTest {
|
||||
|
||||
ConfigurationEntryStoreFactory storeFactory = new InMemoryConfigurationEntryStoreFactory();
|
||||
RepositoryUpdateIterator repositoryUpdateIterator = mock(RepositoryUpdateIterator.class, CALLS_REAL_METHODS);
|
||||
|
||||
List<String> processedUpdates = new ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
void mockRepositories() {
|
||||
doAnswer(invocation -> {
|
||||
Consumer<String> consumer = invocation.getArgument(0, Consumer.class);
|
||||
consumer.accept("42");
|
||||
consumer.accept("1337");
|
||||
return null;
|
||||
}).when(repositoryUpdateIterator).forEachRepository(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessStepsInCorrectOrder() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
@@ -51,13 +73,45 @@ class UpdateEngineTest {
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.1.0", "test:1.1.1", "test:1.2.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessStepsInCorrectOrderWithRepositoryUpdates() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
LinkedHashSet<RepositoryUpdateStep> repositoryUpdateSteps = new LinkedHashSet<>();
|
||||
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, storeFactory, repositoryUpdateIterator);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.1.0-42", "test:1.1.0-1337", "test:1.1.1-42", "test:1.1.1-1337", "test:1.2.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessStepsForSingleRepository() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
LinkedHashSet<RepositoryUpdateStep> repositoryUpdateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, storeFactory, repositoryUpdateIterator);
|
||||
updateEngine.update("1337");
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.1.0-1337", "test:1.1.1-1337");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessCoreStepsBeforeOther() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
@@ -65,7 +119,7 @@ class UpdateEngineTest {
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
updateSteps.add(new CoreFixedVersionUpdateStep("core", "1.2.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
@@ -73,41 +127,73 @@ class UpdateEngineTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunStepsOnlyOnce() {
|
||||
void shouldProcessGlobalStepsBeforeRepository() {
|
||||
Set<UpdateStep> updateSteps = singleton(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
Set<RepositoryUpdateStep> repositoryUpdateSteps = singleton(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, repositoryUpdateSteps, storeFactory, repositoryUpdateIterator);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.2.0", "test:1.2.0-42", "test:1.2.0-1337");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunGlobalStepsOnlyOnce() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
updateEngine.update();
|
||||
UpdateEngine firstUpdateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator);
|
||||
firstUpdateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
updateEngine.update();
|
||||
UpdateEngine secondUpdateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator);
|
||||
secondUpdateEngine.update();
|
||||
|
||||
assertThat(processedUpdates).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunRepositoryStepsOnlyOnce() {
|
||||
LinkedHashSet<RepositoryUpdateStep> repositoryUpdateSteps = new LinkedHashSet<>();
|
||||
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine firstUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, storeFactory, repositoryUpdateIterator);
|
||||
firstUpdateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
repositoryUpdateSteps.add(new FixedVersionUpdateStep("test", "1.2.0"));
|
||||
UpdateEngine secondUpdateEngine = new UpdateEngine(emptySet(), repositoryUpdateSteps, storeFactory, repositoryUpdateIterator);
|
||||
secondUpdateEngine.update();
|
||||
|
||||
assertThat(processedUpdates)
|
||||
.containsExactly("test:1.2.0-42", "test:1.2.0-1337");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRunStepsForDifferentTypesIndependently() {
|
||||
LinkedHashSet<UpdateStep> updateSteps = new LinkedHashSet<>();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("test", "1.1.1"));
|
||||
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
UpdateEngine updateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator);
|
||||
updateEngine.update();
|
||||
|
||||
processedUpdates.clear();
|
||||
|
||||
updateSteps.add(new FixedVersionUpdateStep("other", "1.1.1"));
|
||||
|
||||
updateEngine = new UpdateEngine(updateSteps, storeFactory);
|
||||
updateEngine = new UpdateEngine(updateSteps, emptySet(), storeFactory, repositoryUpdateIterator);
|
||||
updateEngine.update();
|
||||
|
||||
assertThat(processedUpdates).containsExactly("other:1.1.1");
|
||||
}
|
||||
|
||||
class FixedVersionUpdateStep implements UpdateStep {
|
||||
class FixedVersionUpdateStep implements UpdateStep, RepositoryUpdateStep {
|
||||
private final String type;
|
||||
private final String version;
|
||||
|
||||
@@ -130,6 +216,11 @@ class UpdateEngineTest {
|
||||
public void doUpdate() {
|
||||
processedUpdates.add(type + ":" + version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdate(RepositoryUpdateContext repositoryUpdateContext) throws Exception {
|
||||
processedUpdates.add(type + ":" + version + "-" + repositoryUpdateContext.getRepositoryId());
|
||||
}
|
||||
}
|
||||
|
||||
class CoreFixedVersionUpdateStep extends FixedVersionUpdateStep implements CoreUpdateStep {
|
||||
|
||||
Reference in New Issue
Block a user