From 6fbde760d49e63fed1d1addc50ae4a08b1f35b8b Mon Sep 17 00:00:00 2001 From: Thomas Zerr Date: Tue, 30 Apr 2024 15:28:08 +0200 Subject: [PATCH] Fix double /trunk folder bug With this change, every repository initializer gets its own modification command and therefore its own commit. This allows each initializer to decide on its own, if he want to use the default path of the repository. --- .../changed_initializer_commits.yaml | 2 ++ .../RepositoryContentInitializer.java | 8 +++++ .../repository/api/ModifyCommandBuilder.java | 4 +++ .../repository/spi/ModifyCommandRequest.java | 4 +++ .../scm/repository/RepositoryInitializer.java | 22 +++++++++----- .../repository/RepositoryInitializerTest.java | 29 +++++++++++++++++-- 6 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 gradle/changelog/changed_initializer_commits.yaml diff --git a/gradle/changelog/changed_initializer_commits.yaml b/gradle/changelog/changed_initializer_commits.yaml new file mode 100644 index 0000000000..88441e507e --- /dev/null +++ b/gradle/changelog/changed_initializer_commits.yaml @@ -0,0 +1,2 @@ +- type: changed + description: Each repository initializer step is now executed with his own commit diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryContentInitializer.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryContentInitializer.java index a6eeab1004..cec6f24fe7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryContentInitializer.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryContentInitializer.java @@ -59,6 +59,14 @@ public interface RepositoryContentInitializer { */ CreateFile create(String path); + /** + * create new file which will be included in initial repository commit. + * + * @param path full path of the file to be created + * @param useDefaultPath Wether the default path of the repository should be prefixed to the specified path. For example "/trunk", if it is a SVN repository. + */ + CreateFile createWithDefaultPath(String path, boolean useDefaultPath); + /** * Returns the context entry with the given key and unmarshalls it to the given type. * It no entry with the given key is available an empty optional is returned. diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java index 03913dd54f..4de502d807 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/ModifyCommandBuilder.java @@ -157,6 +157,10 @@ public class ModifyCommandBuilder { } } + public boolean isEmpty() { + return request.isEmpty(); + } + /** * Set the commit message for the new commit. * @return This builder instance. diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java index 6a255fe645..848b09503e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyCommandRequest.java @@ -65,6 +65,10 @@ public class ModifyCommandRequest implements Resetable, Validateable, CommandWit this.requests.add(request); } + public boolean isEmpty() { + return requests.isEmpty(); + } + public void setAuthor(Person author) { this.author = author; } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryInitializer.java b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryInitializer.java index 1e1e3922a9..3ff4c4bda7 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryInitializer.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryInitializer.java @@ -60,18 +60,19 @@ public class RepositoryInitializer { public void initialize(Repository repository, Map contextEntries) { try (RepositoryService service = serviceFactory.create(repository)) { - ModifyCommandBuilder modifyCommandBuilder = service.getModifyCommand(); - - InitializerContextImpl initializerContext = new InitializerContextImpl(repository, modifyCommandBuilder, contextEntries); for (RepositoryContentInitializer initializer : contentInitializers) { + ModifyCommandBuilder modifyCommandBuilder = service.getModifyCommand(); + InitializerContextImpl initializerContext = new InitializerContextImpl(repository, modifyCommandBuilder, contextEntries); + initializer.initialize(initializerContext); + + if (!modifyCommandBuilder.isEmpty()) { + modifyCommandBuilder.setCommitMessage("initialize repository"); + String revision = modifyCommandBuilder.execute(); + LOG.info("initialized repository {} as revision {}", repository, revision); + } } - - modifyCommandBuilder.setCommitMessage("initialize repository"); - String revision = modifyCommandBuilder.execute(); - LOG.info("initialized repository {} as revision {}", repository, revision); - } catch (IOException e) { throw new InternalRepositoryException(repository, "failed to initialize repository", e); } @@ -109,6 +110,11 @@ public class RepositoryInitializer { public RepositoryContentInitializer.CreateFile create(String path) { return new CreateFileImpl(this, builder.useDefaultPath(true).createFile(path).setOverwrite(true)); } + + @Override + public RepositoryContentInitializer.CreateFile createWithDefaultPath(String path, boolean useDefaultPath) { + return new CreateFileImpl(this, builder.useDefaultPath(useDefaultPath).createFile(path).setOverwrite(true)); + } } private static class CreateFileImpl implements RepositoryContentInitializer.CreateFile { diff --git a/scm-webapp/src/test/java/sonia/scm/repository/RepositoryInitializerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryInitializerTest.java index d0cc9cdd29..ebafa7f32e 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/RepositoryInitializerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryInitializerTest.java @@ -79,6 +79,23 @@ class RepositoryInitializerTest { when(repositoryService.getModifyCommand()).thenReturn(modifyCommand); } + @Test + void shouldNotCommitIfInitializerDidNotMakeAnyChanges() { + when(modifyCommand.isEmpty()).thenReturn(true); + + Set repositoryContentInitializers = ImmutableSet.of( + new NoOpInitializer() + ); + + RepositoryInitializer initializer = new RepositoryInitializer(repositoryServiceFactory, repositoryContentInitializers); + initializer.initialize(repository, Collections.emptyMap()); + + verify(modifyCommand, never()).setCommitMessage("initialize repository"); + verify(modifyCommand, never()).execute(); + + verify(repositoryService).close(); + } + @Test void shouldCallRepositoryContentInitializer() throws IOException { ModifyCommandBuilder.WithOverwriteFlagContentLoader readmeContentLoader = mockContentLoader("README.md"); @@ -95,8 +112,8 @@ class RepositoryInitializerTest { verifyFileCreation(readmeContentLoader, "# HeartOfGold"); verifyFileCreation(licenseContentLoader, "MIT"); - verify(modifyCommand).setCommitMessage("initialize repository"); - verify(modifyCommand).execute(); + verify(modifyCommand, times(2)).setCommitMessage("initialize repository"); + verify(modifyCommand, times(2)).execute(); verify(repositoryService).close(); } @@ -257,6 +274,14 @@ class RepositoryInitializerTest { } } + @Priority(3) + private static class NoOpInitializer implements RepositoryContentInitializer { + + @Override + public void initialize(InitializerContext context) { + } + } + private static class StreamingContentInitializer implements RepositoryContentInitializer { @Override