From a795129c70be92ecf122317a3b445df3911c8ec5 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 19 Nov 2019 08:48:46 +0100 Subject: [PATCH 01/35] Remove global style: last div two levels below .modal-card-body Apparently added for the CI plugin on 2019-07-24, but no longer necessary --- scm-ui/ui-styles/src/scm.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scm-ui/ui-styles/src/scm.scss b/scm-ui/ui-styles/src/scm.scss index 97e3a70a9f..01d8d3e55a 100644 --- a/scm-ui/ui-styles/src/scm.scss +++ b/scm-ui/ui-styles/src/scm.scss @@ -809,10 +809,6 @@ form .field:not(.is-grouped) { } } -.modal-card-body div div:last-child { - border-bottom: none; -} - // cursor .has-cursor-pointer { cursor: pointer; From 3a3c27bb6768a0beddc7a612be3a985c976b9e50 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Tue, 19 Nov 2019 09:26:01 +0100 Subject: [PATCH 02/35] Enhance logging for diff comparison --- .../test/java/sonia/scm/it/DiffITCase.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/scm-it/src/test/java/sonia/scm/it/DiffITCase.java b/scm-it/src/test/java/sonia/scm/it/DiffITCase.java index acd2816422..a9d99deab2 100644 --- a/scm-it/src/test/java/sonia/scm/it/DiffITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/DiffITCase.java @@ -1,6 +1,7 @@ package sonia.scm.it; import org.apache.http.HttpStatus; +import org.assertj.core.api.AbstractCharSequenceAssert; import org.assertj.core.util.Lists; import org.junit.Before; import org.junit.Ignore; @@ -28,6 +29,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static sonia.scm.it.utils.RestUtil.ADMIN_PASSWORD; import static sonia.scm.it.utils.RestUtil.ADMIN_USERNAME; @@ -94,8 +96,7 @@ public class DiffITCase { String gitDiff = getDiff(RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), gitRepositoryResponse); String expected = getGitDiffWithoutIndexLine(gitDiff); - assertThat(svnDiff) - .isEqualTo(expected); + assertDiffsAreEqual(svnDiff, expected); } @Test @@ -107,8 +108,7 @@ public class DiffITCase { String gitDiff = getDiff(RepositoryUtil.removeAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt"), gitRepositoryResponse); String expected = getGitDiffWithoutIndexLine(gitDiff); - assertThat(svnDiff) - .isEqualTo(expected); + assertDiffsAreEqual(svnDiff, expected); } @Test @@ -120,8 +120,7 @@ public class DiffITCase { String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "the updated content of a"), gitRepositoryResponse); String expected = getGitDiffWithoutIndexLine(gitDiff); - assertThat(svnDiff) - .isEqualTo(expected); + assertDiffsAreEqual(svnDiff, expected); } @Test @@ -161,21 +160,17 @@ public class DiffITCase { String fileContent = getFileContent("/diff/largefile/original/SvnDiffGenerator_forTest"); String svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse); String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse); - assertThat(svnDiff) - .isEqualTo(getGitDiffWithoutIndexLine(gitDiff)); + assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff)); fileContent = getFileContent("/diff/largefile/modified/v1/SvnDiffGenerator_forTest"); svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse); gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse); - assertThat(svnDiff) - .isEqualTo(getGitDiffWithoutIndexLine(gitDiff)); + assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff)); fileContent = getFileContent("/diff/largefile/modified/v2/SvnDiffGenerator_forTest"); svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse); gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse); - assertThat(svnDiff) - .isEqualTo(getGitDiffWithoutIndexLine(gitDiff)); - + assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff)); } /** @@ -196,8 +191,7 @@ public class DiffITCase { Changeset commit1 = RepositoryUtil.addFileAndCommit(gitRepositoryClient, fileName, ADMIN_USERNAME, ""); String svnDiff = getDiff(commit, svnRepositoryResponse); String gitDiff = getDiff(commit1, gitRepositoryResponse); - assertThat(svnDiff) - .isEqualTo(getGitDiffWithoutIndexLine(gitDiff)); + assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff)); } @@ -218,8 +212,7 @@ public class DiffITCase { String gitDiff = getDiff(RepositoryUtil.addFileAndCommit(gitRepositoryClient, newFileName, ADMIN_USERNAME, "renamed file"), gitRepositoryResponse); String expected = getGitDiffWithoutIndexLine(gitDiff); - assertThat(svnDiff) - .isEqualTo(expected); + assertDiffsAreEqual(svnDiff, expected); } public String getFileContent(String name) throws URISyntaxException, IOException { @@ -242,6 +235,12 @@ public class DiffITCase { return gitDiff.replaceAll(".*(index.*\n)", ""); } + private void assertDiffsAreEqual(String svnDiff, String gitDiff) { + assertThat(svnDiff) + .as("diffs are different\n\nsvn:\n==================================================\n\n%s\n\ngit:\n==================================================\n\n%s)", svnDiff, gitDiff) + .isEqualTo(gitDiff); + } + private String getDiff(Changeset svnChangeset, ScmRequests.RepositoryResponse svnRepositoryResponse) { return svnRepositoryResponse.requestChangesets() .requestDiffInGitFormat(svnChangeset.getId()) From 1d2888f8e65ad5b2ac3f049cfb00a82252e42652 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Tue, 19 Nov 2019 10:13:58 +0100 Subject: [PATCH 03/35] Fix dequote error in "normal" lines Without this, quotes in normal lines were removed. --- .../main/java/sonia/scm/repository/spi/GitDiffCommand.java | 1 + .../spi/GitDiffCommand_DequoteOutputStreamTest.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java index d6203f2a1b..dce4d0622f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java @@ -110,6 +110,7 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { return; } numberOfPotentialBeginning = -1; + inPotentialQuotedLine = false; } if (inPotentialQuotedLine && i == '"') { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java index 8eaab4fc16..6067356a09 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommand_DequoteOutputStreamTest.java @@ -16,7 +16,7 @@ public class GitDiffCommand_DequoteOutputStreamTest { "--- /dev/null\n" + "+++ \"b/\\303\\272\\303\\274\\303\\276\\303\\253\\303\\251\\303\\245\\303\\253\\303\\245\\303\\251 \\303\\245g\\303\\260f\\303\\237\"\n" + "@@ -0,0 +1 @@\n" + - "+rthms"; + "+String s = \"quotes shall be kept\";"; ByteArrayOutputStream buffer = new ByteArrayOutputStream(); GitDiffCommand.DequoteOutputStream stream = new GitDiffCommand.DequoteOutputStream(buffer); @@ -30,6 +30,6 @@ public class GitDiffCommand_DequoteOutputStreamTest { "--- /dev/null\n" + "+++ b/úüþëéåëåé ågðfß\n" + "@@ -0,0 +1 @@\n" + - "+rthms"); + "+String s = \"quotes shall be kept\";"); } } From dd8f84e7c4e947aaf58110af6f557d72dbd668c8 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 19 Nov 2019 13:50:57 +0100 Subject: [PATCH 04/35] implement repository public flag migration to repositoryPermissions for _anonymous user --- .../repository/PublicFlagUpdateStep.java | 97 ++++++++++++++ .../scm/update/repository/V1Repository.java | 2 + .../update/repository/V1RepositoryHelper.java | 51 ++++++++ .../repository/XmlRepositoryV1UpdateStep.java | 52 ++------ .../repository/PublicFlagUpdateStepTest.java | 118 ++++++++++++++++++ .../scm/update/repository/scm-home.v1.zip | Bin 13593 -> 14520 bytes 6 files changed, 277 insertions(+), 43 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java create mode 100644 scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java create mode 100644 scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java new file mode 100644 index 0000000000..b2e3c17980 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java @@ -0,0 +1,97 @@ +package sonia.scm.update.repository; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.SCMContext; +import sonia.scm.SCMContextProvider; +import sonia.scm.migration.UpdateStep; +import sonia.scm.plugin.Extension; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.xml.XmlRepositoryDAO; +import sonia.scm.user.User; +import sonia.scm.user.xml.XmlUserDAO; +import sonia.scm.version.Version; + +import javax.inject.Inject; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; + +import static sonia.scm.version.Version.parse; + +@Extension +public class PublicFlagUpdateStep implements UpdateStep { + + private static final Logger LOG = LoggerFactory.getLogger(PublicFlagUpdateStep.class); + + private static final String V1_REPOSITORY_BACKUP_FILENAME = "repositories.xml.v1.backup"; + + private final SCMContextProvider contextProvider; + private final XmlUserDAO userDAO; + private final XmlRepositoryDAO repositoryDAO; + + @Inject + public PublicFlagUpdateStep(SCMContextProvider contextProvider, XmlUserDAO userDAO, XmlRepositoryDAO repositoryDAO) { + this.contextProvider = contextProvider; + this.userDAO = userDAO; + this.repositoryDAO = repositoryDAO; + } + + @Override + public void doUpdate() throws JAXBException { + createNewAnonymousUserIfNotExists(); + deleteOldAnonymousUserIfAvailable(); + + JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class); + LOG.info("Migrating public flags of repositories as RepositoryRolePermission 'READ' for user '_anonymous'"); + V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_BACKUP_FILENAME).ifPresent( + this::addRepositoryReadPermissionForAnonymousUser + ); + } + + @Override + public Version getTargetVersion() { + return parse("2.0.3"); + } + + @Override + public String getAffectedDataType() { + return "sonia.scm.repository.xml"; + } + + private void addRepositoryReadPermissionForAnonymousUser(V1RepositoryHelper.V1RepositoryDatabase v1RepositoryDatabase) { + User v2AnonymousUser = userDAO.get(SCMContext.USER_ANONYMOUS); + v1RepositoryDatabase.repositoryList.repositories + .stream() + .filter(V1Repository::isPublic) + .forEach(v1Repository -> { + Repository v2Repository = repositoryDAO.get(v1Repository.getId()); + LOG.info(String.format("Add RepositoryRole 'READ' to _anonymous user for repository: %s - %s/%s", v2Repository.getId(), v2Repository.getNamespace(), v2Repository.getName())); + v2Repository.addPermission(new RepositoryPermission(v2AnonymousUser.getId(), "READ", false)); + repositoryDAO.modify(v2Repository); + }); + } + + private void createNewAnonymousUserIfNotExists() { + if (!userExists(SCMContext.USER_ANONYMOUS)) { + LOG.info("Create new _anonymous user"); + userDAO.add(SCMContext.ANONYMOUS); + } + } + + private void deleteOldAnonymousUserIfAvailable() { + String oldAnonymous = "anonymous"; + if (userExists(oldAnonymous)) { + User anonymousUser = userDAO.get(oldAnonymous); + LOG.info("Delete obsolete anonymous user"); + userDAO.delete(anonymousUser); + } + } + + private boolean userExists(String username) { + return userDAO + .getAll() + .stream() + .anyMatch(user -> user.getName().equals(username)); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java index 4ce823bd33..8b389e467b 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1Repository.java @@ -4,6 +4,7 @@ import sonia.scm.update.V1Properties; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.util.List; @@ -16,6 +17,7 @@ public class V1Repository { private String description; private String id; private String name; + @XmlElement(name="public") private boolean isPublic; private boolean archived; private String type; diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java new file mode 100644 index 0000000000..bba89b541f --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java @@ -0,0 +1,51 @@ +package sonia.scm.update.repository; + +import sonia.scm.SCMContextProvider; +import sonia.scm.store.StoreConstants; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.File; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; + +class V1RepositoryHelper { + + static File resolveV1File(SCMContextProvider contextProvider, String filename) { + return contextProvider + .resolve( + Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(filename) + ).toFile(); + } + + static Optional readV1Database(JAXBContext jaxbContext, SCMContextProvider contextProvider, String filename) throws JAXBException { + Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File(contextProvider,filename)); + if (unmarshal instanceof V1RepositoryHelper.V1RepositoryDatabase) { + return of((V1RepositoryHelper.V1RepositoryDatabase) unmarshal); + } else { + return empty(); + } + } + + static class RepositoryList { + @XmlElement(name = "repository") + List repositories; + } + + @XmlRootElement(name = "repository-db") + @XmlAccessorType(XmlAccessType.FIELD) + static class V1RepositoryDatabase { + long creationTime; + Long lastModified; + @XmlElement(name = "repositories") + RepositoryList repositoryList; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java index a2f7656498..69354c294a 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java @@ -19,24 +19,17 @@ import sonia.scm.version.Version; import javax.inject.Inject; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; -import javax.xml.bind.annotation.XmlAccessType; -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.nio.file.Paths; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Collections.emptyList; -import static java.util.Optional.empty; -import static java.util.Optional.of; import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER; +import static sonia.scm.update.repository.V1RepositoryHelper.resolveV1File; import static sonia.scm.version.Version.parse; /** @@ -59,6 +52,8 @@ import static sonia.scm.version.Version.parse; @Extension public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { + private final String V1_REPOSITORY_FILENAME = "repositories" + StoreConstants.FILE_EXTENSION; + private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class); private final SCMContextProvider contextProvider; @@ -97,12 +92,12 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { @Override public void doUpdate() throws JAXBException { - if (!resolveV1File().exists()) { + if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).exists()) { LOG.info("no v1 repositories database file found"); return; } - JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class); - readV1Database(jaxbContext).ifPresent( + JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class); + V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_FILENAME).ifPresent( v1Database -> { v1Database.repositoryList.repositories.forEach(this::readMigrationEntry); v1Database.repositoryList.repositories.forEach(this::update); @@ -112,13 +107,13 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { } public List getRepositoriesWithoutMigrationStrategies() { - if (!resolveV1File().exists()) { + if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).exists()) { LOG.info("no v1 repositories database file found"); return emptyList(); } try { - JAXBContext jaxbContext = JAXBContext.newInstance(XmlRepositoryV1UpdateStep.V1RepositoryDatabase.class); - return readV1Database(jaxbContext) + JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class); + return V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_FILENAME) .map(v1Database -> v1Database.repositoryList.repositories.stream()) .orElse(Stream.empty()) .filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent()) @@ -196,33 +191,4 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { return new RepositoryPermission(v1Permission.getName(), v1Permission.getType(), v1Permission.isGroupPermission()); } - private Optional readV1Database(JAXBContext jaxbContext) throws JAXBException { - Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File()); - if (unmarshal instanceof V1RepositoryDatabase) { - return of((V1RepositoryDatabase) unmarshal); - } else { - return empty(); - } - } - - private File resolveV1File() { - return contextProvider - .resolve( - Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve("repositories" + StoreConstants.FILE_EXTENSION) - ).toFile(); - } - - private static class RepositoryList { - @XmlElement(name = "repository") - private List repositories; - } - - @XmlRootElement(name = "repository-db") - @XmlAccessorType(XmlAccessType.FIELD) - private static class V1RepositoryDatabase { - private long creationTime; - private Long lastModified; - @XmlElement(name = "repositories") - private RepositoryList repositoryList; - } } diff --git a/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java new file mode 100644 index 0000000000..68edc760c3 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java @@ -0,0 +1,118 @@ +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.junitpioneer.jupiter.TempDirectory; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.SCMContext; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermission; +import sonia.scm.repository.RepositoryRolePermissions; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.repository.xml.XmlRepositoryDAO; +import sonia.scm.update.UpdateStepTestUtil; +import sonia.scm.user.User; +import sonia.scm.user.xml.XmlUserDAO; + +import javax.xml.bind.JAXBException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junitpioneer.jupiter.TempDirectory.TempDir; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@ExtendWith(TempDirectory.class) +class PublicFlagUpdateStepTest { + + @Mock + XmlUserDAO userDAO; + @Mock + XmlRepositoryDAO repositoryDAO; + @Captor + ArgumentCaptor repositoryCaptor; + + private UpdateStepTestUtil testUtil; + private PublicFlagUpdateStep updateStep; + private Repository REPOSITORY = RepositoryTestData.createHeartOfGold(); + + @BeforeEach + void mockScmHome(@TempDir Path tempDir) throws IOException { + testUtil = new UpdateStepTestUtil(tempDir); + updateStep = new PublicFlagUpdateStep(testUtil.getContextProvider(), userDAO, repositoryDAO); + + //prepare backup xml + V1RepositoryFileSystem.createV1Home(tempDir); + Files.move(tempDir.resolve("config").resolve("repositories.xml"), tempDir.resolve("config").resolve("repositories.xml.v1.backup")); + when(repositoryDAO.get((String) any())).thenReturn(REPOSITORY); + } + + @Test + void shouldDeleteOldAnonymousUserIfExists() throws JAXBException { + when(userDAO.getAll()).thenReturn(Collections.singleton(new User("anonymous"))); + User anonymous = new User("anonymous"); + doReturn(anonymous).when(userDAO).get("anonymous"); + doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS); + + updateStep.doUpdate(); + + verify(userDAO).delete(anonymous); + } + + @Test + void shouldNotTryToDeleteOldAnonymousUserIfNotExists() throws JAXBException { + when(userDAO.getAll()).thenReturn(Collections.emptyList()); + doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS); + + updateStep.doUpdate(); + + verify(userDAO, never()).delete(any()); + } + + @Test + void shouldCreateNewAnonymousUserIfNotExists() throws JAXBException { + doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS); + when(userDAO.getAll()).thenReturn(Collections.singleton(new User("trillian"))); + + updateStep.doUpdate(); + + verify(userDAO).add(SCMContext.ANONYMOUS); + } + + @Test + void shouldNotCreateNewAnonymousUserIfAlreadyExists() throws JAXBException { + doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS); + when(userDAO.getAll()).thenReturn(Collections.singleton(new User("_anonymous"))); + + updateStep.doUpdate(); + + verify(userDAO, never()).add(SCMContext.ANONYMOUS); + } + + @Test + void shouldMigratePublicFlagToAnonymousRepositoryPermission() throws JAXBException { + when(userDAO.getAll()).thenReturn(Collections.emptyList()); + when(userDAO.get("_anonymous")).thenReturn(SCMContext.ANONYMOUS); + + updateStep.doUpdate(); + + verify(repositoryDAO, times(2)).modify(repositoryCaptor.capture()); + + RepositoryPermission migratedRepositoryPermission = repositoryCaptor.getValue().getPermissions().iterator().next(); + assertThat(migratedRepositoryPermission.getName()).isEqualTo(SCMContext.USER_ANONYMOUS); + assertThat(migratedRepositoryPermission.getRole()).isEqualTo("READ"); + assertThat(migratedRepositoryPermission.isGroupPermission()).isFalse(); + } +} 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 5d936db5e264ac6182855e34a5aae82871c2c3fd..4b21785f20ee59325f4b6208385143a4abe44e1d 100644 GIT binary patch literal 14520 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 Pv0rnsyXu93Fc|*>fMK>$ literal 13593 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 Date: Tue, 19 Nov 2019 14:04:47 +0100 Subject: [PATCH 05/35] remove repository public flag --- .../scm/repository/AbstactImportHandler.java | 1 - .../java/sonia/scm/repository/Repository.java | 19 +------------------ .../RepositoryDtoToRepositoryMapper.java | 1 - .../AuthorizationChangedEventProducer.java | 3 +-- .../MigrateVerbsToPermissionRoles.java | 1 - 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java index 1f1e7cfefb..63d4638ab8 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java @@ -113,7 +113,6 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler Repository repository = new Repository(); repository.setName(repositoryName); - repository.setPublicReadable(false); repository.setType(getTypeName()); return repository; diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 463085a7ea..6b7df70081 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -84,7 +84,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per @XmlElement(name = "permission") private Set permissions = new HashSet<>(); @XmlElement(name = "public") - private boolean publicReadable = false; private String type; @@ -225,15 +224,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return Util.isEmpty(healthCheckFailures); } - /** - * Returns true if the {@link Repository} is public readable. - * - * @return true if the {@link Repository} is public readable - */ - public boolean isPublicReadable() { - return publicReadable; - } - /** * Returns true if the {@link Repository} is valid. *
    @@ -292,10 +282,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return this.permissions.remove(permission); } - public void setPublicReadable(boolean publicReadable) { - this.publicReadable = publicReadable; - } - public void setType(String type) { this.type = type; } @@ -332,7 +318,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per repository.setLastModified(lastModified); repository.setDescription(description); repository.setPermissions(permissions); - repository.setPublicReadable(publicReadable); // do not copy health check results } @@ -360,7 +345,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per && Objects.equal(name, other.name) && Objects.equal(contact, other.contact) && Objects.equal(description, other.description) - && Objects.equal(publicReadable, other.publicReadable) && Objects.equal(permissions, other.permissions) && Objects.equal(type, other.type) && Objects.equal(creationDate, other.creationDate) @@ -371,7 +355,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per @Override public int hashCode() { - return Objects.hashCode(id, namespace, name, contact, description, publicReadable, + return Objects.hashCode(id, namespace, name, contact, description, permissions, type, creationDate, lastModified, properties, healthCheckFailures); } @@ -384,7 +368,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per .add("name", name) .add("contact", contact) .add("description", description) - .add("publicReadable", publicReadable) .add("permissions", permissions) .add("type", type) .add("lastModified", lastModified) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java index b9add14529..ab36672f83 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java @@ -8,7 +8,6 @@ public abstract class RepositoryDtoToRepositoryMapper extends BaseDtoMapper { @Mapping(target = "creationDate", ignore = true) @Mapping(target = "id", ignore = true) - @Mapping(target = "publicReadable", ignore = true) @Mapping(target = "healthCheckFailures", ignore = true) public abstract Repository map(RepositoryDto repositoryDto, @Context String id); diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java index fc653efa52..6914f89286 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java @@ -168,8 +168,7 @@ public class AuthorizationChangedEventProducer { } private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) { - return repository.isPublicReadable() != beforeModification.isPublicReadable() - || !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions())); + return !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions())); } private void fireEventForEveryUser() { diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java b/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java index be40ab3a6d..fc2dec12a6 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java @@ -90,7 +90,6 @@ public class MigrateVerbsToPermissionRoles implements UpdateStep { repository.setCreationDate(oldRepository.creationDate); repository.setHealthCheckFailures(oldRepository.healthCheckFailures); repository.setLastModified(oldRepository.lastModified); - repository.setPublicReadable(oldRepository.publicReadable); return repository; } From e432d6d210c31435160a0f2cc098f8cb785acdfe Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 19 Nov 2019 14:23:05 +0100 Subject: [PATCH 06/35] refactor --- .../src/main/java/sonia/scm/repository/Repository.java | 1 - .../sonia/scm/update/repository/PublicFlagUpdateStep.java | 4 +--- .../sonia/scm/update/repository/V1RepositoryHelper.java | 5 +++-- .../scm/update/repository/XmlRepositoryV1UpdateStep.java | 7 ++----- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 6b7df70081..e35e12bbb0 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -83,7 +83,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per private String name; @XmlElement(name = "permission") private Set permissions = new HashSet<>(); - @XmlElement(name = "public") private String type; diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java index b2e3c17980..5eedce12d0 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java @@ -14,7 +14,6 @@ import sonia.scm.user.xml.XmlUserDAO; import sonia.scm.version.Version; import javax.inject.Inject; -import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import static sonia.scm.version.Version.parse; @@ -42,9 +41,8 @@ public class PublicFlagUpdateStep implements UpdateStep { createNewAnonymousUserIfNotExists(); deleteOldAnonymousUserIfAvailable(); - JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class); LOG.info("Migrating public flags of repositories as RepositoryRolePermission 'READ' for user '_anonymous'"); - V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_BACKUP_FILENAME).ifPresent( + V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_BACKUP_FILENAME).ifPresent( this::addRepositoryReadPermissionForAnonymousUser ); } diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java index bba89b541f..43ddb2671b 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java @@ -26,8 +26,9 @@ class V1RepositoryHelper { ).toFile(); } - static Optional readV1Database(JAXBContext jaxbContext, SCMContextProvider contextProvider, String filename) throws JAXBException { - Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File(contextProvider,filename)); + static Optional readV1Database(SCMContextProvider contextProvider, String filename) throws JAXBException { + JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class); + Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File(contextProvider, filename)); if (unmarshal instanceof V1RepositoryHelper.V1RepositoryDatabase) { return of((V1RepositoryHelper.V1RepositoryDatabase) unmarshal); } else { diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java index 69354c294a..6230919503 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java @@ -17,7 +17,6 @@ import sonia.scm.update.V1Properties; import sonia.scm.version.Version; import javax.inject.Inject; -import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import java.io.IOException; import java.nio.file.Files; @@ -96,8 +95,7 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { LOG.info("no v1 repositories database file found"); return; } - JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class); - V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_FILENAME).ifPresent( + V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_FILENAME).ifPresent( v1Database -> { v1Database.repositoryList.repositories.forEach(this::readMigrationEntry); v1Database.repositoryList.repositories.forEach(this::update); @@ -112,8 +110,7 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { return emptyList(); } try { - JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class); - return V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_FILENAME) + return V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_FILENAME) .map(v1Database -> v1Database.repositoryList.repositories.stream()) .orElse(Stream.empty()) .filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent()) From e7cb674bb5fa81db7e5ecf6f4c7d372957a18bfb Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 19 Nov 2019 14:27:14 +0100 Subject: [PATCH 07/35] fix anonymous email --- scm-core/src/main/java/sonia/scm/SCMContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/SCMContext.java b/scm-core/src/main/java/sonia/scm/SCMContext.java index 42d311ed2a..f5367625bb 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContext.java +++ b/scm-core/src/main/java/sonia/scm/SCMContext.java @@ -59,7 +59,7 @@ public final class SCMContext */ public static final User ANONYMOUS = new User(USER_ANONYMOUS, "SCM Anonymous", - "scm-anonymous@scm-manager.com"); + "scm-anonymous@scm-manager.org"); /** Singleton instance of {@link SCMContextProvider} */ private static volatile SCMContextProvider provider; From aa7b6f528223ec74406c7a30902a2698be8dd0eb Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 19 Nov 2019 15:42:22 +0100 Subject: [PATCH 08/35] only migrate public flag if repository-v1-xml-backup --- .../MigrateVerbsToPermissionRoles.java | 2 -- .../update/repository/V1RepositoryHelper.java | 25 +++++++++++-------- .../repository/XmlRepositoryV1UpdateStep.java | 4 +-- .../it/RepositorySimplePermissionITCase.java | 1 - 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java b/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java index fc2dec12a6..f33f4200c4 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/MigrateVerbsToPermissionRoles.java @@ -148,8 +148,6 @@ public class MigrateVerbsToPermissionRoles implements UpdateStep { private String name; @XmlElement(name = "permission") private final Set permissions = new HashSet<>(); - @XmlElement(name = "public") - private boolean publicReadable = false; private boolean archived = false; private String type; } diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java index 43ddb2671b..10cf343cff 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java @@ -19,21 +19,26 @@ import static java.util.Optional.of; class V1RepositoryHelper { - static File resolveV1File(SCMContextProvider contextProvider, String filename) { - return contextProvider - .resolve( - Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(filename) - ).toFile(); + static Optional resolveV1File(SCMContextProvider contextProvider, String filename) { + File v1XmlFile = contextProvider.resolve(Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(filename)).toFile(); + if (v1XmlFile.exists()) { + return Optional.of(v1XmlFile); + } + return Optional.empty(); } static Optional readV1Database(SCMContextProvider contextProvider, String filename) throws JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class); - Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File(contextProvider, filename)); - if (unmarshal instanceof V1RepositoryHelper.V1RepositoryDatabase) { - return of((V1RepositoryHelper.V1RepositoryDatabase) unmarshal); - } else { - return empty(); + Optional file = resolveV1File(contextProvider, filename); + if (file.isPresent()) { + Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(file.get()); + if (unmarshal instanceof V1RepositoryHelper.V1RepositoryDatabase) { + return of((V1RepositoryHelper.V1RepositoryDatabase) unmarshal); + } else { + return empty(); + } } + return empty(); } static class RepositoryList { diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java index 6230919503..dd1284492b 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/XmlRepositoryV1UpdateStep.java @@ -91,7 +91,7 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { @Override public void doUpdate() throws JAXBException { - if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).exists()) { + if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).isPresent()) { LOG.info("no v1 repositories database file found"); return; } @@ -105,7 +105,7 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep { } public List getRepositoriesWithoutMigrationStrategies() { - if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).exists()) { + if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).isPresent()) { LOG.info("no v1 repositories database file found"); return emptyList(); } diff --git a/scm-webapp/src/test/java/sonia/scm/it/RepositorySimplePermissionITCase.java b/scm-webapp/src/test/java/sonia/scm/it/RepositorySimplePermissionITCase.java index 7900f11096..5a0ca8fc38 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/RepositorySimplePermissionITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/RepositorySimplePermissionITCase.java @@ -94,7 +94,6 @@ public class RepositorySimplePermissionITCase repository.setName("test-repo"); repository.setType("git"); -// repository.setPublicReadable(false); ScmClient client = createAdminClient(); From 303458c44e5d1e7737f08d609f5d83840895b59c Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 19 Nov 2019 15:06:12 +0000 Subject: [PATCH 09/35] Close branch bugfix/outdated_context_bottom_border From fd7527717673901827216b51c56ad09da1679acc Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 19 Nov 2019 16:43:45 +0100 Subject: [PATCH 10/35] cleanup --- scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java | 1 + .../java/sonia/scm/update/repository/V1RepositoryHelper.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java index 1b48da792b..6765f0e2d7 100644 --- a/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java @@ -53,6 +53,7 @@ class AnonymousAccessITCase { @Test void shouldRejectRepositoryResourceWithoutAuthentication() { + setAnonymousAccess(false); assertEquals(401, RestAssured.given() .when() .get(RestUtil.REST_BASE_URL.resolve("repositories/")) diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java index 10cf343cff..f2a60c5529 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/V1RepositoryHelper.java @@ -32,8 +32,8 @@ class V1RepositoryHelper { Optional file = resolveV1File(contextProvider, filename); if (file.isPresent()) { Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(file.get()); - if (unmarshal instanceof V1RepositoryHelper.V1RepositoryDatabase) { - return of((V1RepositoryHelper.V1RepositoryDatabase) unmarshal); + if (unmarshal instanceof V1RepositoryDatabase) { + return of((V1RepositoryDatabase) unmarshal); } else { return empty(); } From 49bdf18fb052dc6e25e7440267b8bda864d5ef5d Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 20 Nov 2019 08:24:42 +0100 Subject: [PATCH 11/35] fix integration tests --- .../test/java/sonia/scm/it/AnonymousAccessITCase.java | 1 - .../scm/update/repository/PublicFlagUpdateStep.java | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java index 6765f0e2d7..1b48da792b 100644 --- a/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/AnonymousAccessITCase.java @@ -53,7 +53,6 @@ class AnonymousAccessITCase { @Test void shouldRejectRepositoryResourceWithoutAuthentication() { - setAnonymousAccess(false); assertEquals(401, RestAssured.given() .when() .get(RestUtil.REST_BASE_URL.resolve("repositories/")) diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java index 5eedce12d0..24bcbc363e 100644 --- a/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/PublicFlagUpdateStep.java @@ -38,12 +38,13 @@ public class PublicFlagUpdateStep implements UpdateStep { @Override public void doUpdate() throws JAXBException { - createNewAnonymousUserIfNotExists(); - deleteOldAnonymousUserIfAvailable(); - LOG.info("Migrating public flags of repositories as RepositoryRolePermission 'READ' for user '_anonymous'"); V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_BACKUP_FILENAME).ifPresent( - this::addRepositoryReadPermissionForAnonymousUser + v1RepositoryDatabase -> { + createNewAnonymousUserIfNotExists(); + deleteOldAnonymousUserIfAvailable(); + addRepositoryReadPermissionForAnonymousUser(v1RepositoryDatabase); + } ); } From 0763ae94406658aa96e01c884189cf0edf479557 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 20 Nov 2019 08:29:05 +0100 Subject: [PATCH 12/35] Add optional heades for api client push and put --- scm-ui/ui-components/src/apiclient.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index babb0b64d7..3fb03b067f 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -80,23 +80,24 @@ class ApiClient { return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure); } - post(url: string, payload?: any, contentType = "application/json") { - return this.httpRequestWithJSONBody("POST", url, contentType, payload); + post(url: string, payload?: any, contentType = "application/json", additionalHeaders = new Headers()) { + return this.httpRequestWithJSONBody("POST", url, contentType, additionalHeaders, payload); } - postBinary(url: string, fileAppender: (p: FormData) => void) { + postBinary(url: string, fileAppender: (p: FormData) => void, additionalHeaders = new Headers()) { const formData = new FormData(); fileAppender(formData); const options: RequestInit = { method: "POST", - body: formData + body: formData, + headers: additionalHeaders }; return this.httpRequestWithBinaryBody(options, url); } - put(url: string, payload: any, contentType = "application/json") { - return this.httpRequestWithJSONBody("PUT", url, contentType, payload); + put(url: string, payload: any, contentType = "application/json", additionalHeaders = new Headers()) { + return this.httpRequestWithJSONBody("PUT", url, contentType, additionalHeaders, payload); } head(url: string) { @@ -115,9 +116,16 @@ class ApiClient { return fetch(createUrl(url), options).then(handleFailure); } - httpRequestWithJSONBody(method: string, url: string, contentType: string, payload?: any): Promise { + httpRequestWithJSONBody( + method: string, + url: string, + contentType: string, + additionalHeaders: Headers, + payload?: any + ): Promise { const options: RequestInit = { - method: method + method: method, + headers: additionalHeaders }; if (payload) { options.body = JSON.stringify(payload); From 18c94352cfbdb0c783574e43d3334bdcf6dd3e1d Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 20 Nov 2019 08:36:18 +0100 Subject: [PATCH 13/35] cleanup --- .../sonia/scm/update/repository/PublicFlagUpdateStepTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java index 68edc760c3..cbd8312642 100644 --- a/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java +++ b/scm-webapp/src/test/java/sonia/scm/update/repository/PublicFlagUpdateStepTest.java @@ -61,8 +61,8 @@ class PublicFlagUpdateStepTest { @Test void shouldDeleteOldAnonymousUserIfExists() throws JAXBException { - when(userDAO.getAll()).thenReturn(Collections.singleton(new User("anonymous"))); User anonymous = new User("anonymous"); + when(userDAO.getAll()).thenReturn(Collections.singleton(anonymous)); doReturn(anonymous).when(userDAO).get("anonymous"); doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS); From 20659bc32e23950e74f2d7d3c1260d3710044cc3 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 20 Nov 2019 08:59:57 +0100 Subject: [PATCH 14/35] Add post and put with text --- scm-ui/ui-components/src/apiclient.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 3fb03b067f..4f2a8a735e 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -84,6 +84,14 @@ class ApiClient { return this.httpRequestWithJSONBody("POST", url, contentType, additionalHeaders, payload); } + postText(url: string, payload: string, additionalHeaders = new Headers()) { + return this.httpRequestWithTextBody("POST", url, additionalHeaders, payload); + } + + putText(url: string, payload: string, additionalHeaders = new Headers()) { + return this.httpRequestWithTextBody("PUT", url, additionalHeaders, payload); + } + postBinary(url: string, fileAppender: (p: FormData) => void, additionalHeaders = new Headers()) { const formData = new FormData(); fileAppender(formData); @@ -133,6 +141,15 @@ class ApiClient { return this.httpRequestWithBinaryBody(options, url, contentType); } + httpRequestWithTextBody(method: string, url: string, additionalHeaders: Headers, payload: string) { + const options: RequestInit = { + method: method, + headers: additionalHeaders + }; + options.body = payload; + return this.httpRequestWithBinaryBody(options, url, "text/plain"); + } + httpRequestWithBinaryBody(options: RequestInit, url: string, contentType?: string) { options = applyFetchOptions(options); if (contentType) { From a2474896cc38c7858f9a4dcf036c7dba3a4ef8ed Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 20 Nov 2019 10:44:11 +0100 Subject: [PATCH 15/35] Fix types --- scm-ui/ui-components/src/apiclient.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 4f2a8a735e..4c1bad8ac1 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -80,19 +80,19 @@ class ApiClient { return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure); } - post(url: string, payload?: any, contentType = "application/json", additionalHeaders = new Headers()) { + post(url: string, payload?: any, contentType = "application/json", additionalHeaders: Record = {}) { return this.httpRequestWithJSONBody("POST", url, contentType, additionalHeaders, payload); } - postText(url: string, payload: string, additionalHeaders = new Headers()) { + postText(url: string, payload: string, additionalHeaders: Record = {}) { return this.httpRequestWithTextBody("POST", url, additionalHeaders, payload); } - putText(url: string, payload: string, additionalHeaders = new Headers()) { + putText(url: string, payload: string, additionalHeaders: Record = {}) { return this.httpRequestWithTextBody("PUT", url, additionalHeaders, payload); } - postBinary(url: string, fileAppender: (p: FormData) => void, additionalHeaders = new Headers()) { + postBinary(url: string, fileAppender: (p: FormData) => void, additionalHeaders: Record = {}) { const formData = new FormData(); fileAppender(formData); @@ -104,7 +104,7 @@ class ApiClient { return this.httpRequestWithBinaryBody(options, url); } - put(url: string, payload: any, contentType = "application/json", additionalHeaders = new Headers()) { + put(url: string, payload: any, contentType = "application/json", additionalHeaders: Record = {}) { return this.httpRequestWithJSONBody("PUT", url, contentType, additionalHeaders, payload); } @@ -128,7 +128,7 @@ class ApiClient { method: string, url: string, contentType: string, - additionalHeaders: Headers, + additionalHeaders: Record, payload?: any ): Promise { const options: RequestInit = { @@ -141,7 +141,12 @@ class ApiClient { return this.httpRequestWithBinaryBody(options, url, contentType); } - httpRequestWithTextBody(method: string, url: string, additionalHeaders: Headers, payload: string) { + httpRequestWithTextBody( + method: string, + url: string, + additionalHeaders: Record = {}, + payload: string + ) { const options: RequestInit = { method: method, headers: additionalHeaders @@ -156,7 +161,7 @@ class ApiClient { if (!options.headers) { options.headers = new Headers(); } - // @ts-ignore + // @ts-ignore We are sure that here we only get headers of type Record options.headers["Content-Type"] = contentType; } From cf17e07cea26b852d2a09d0390e1f20a569729cc Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 20 Nov 2019 11:12:22 +0100 Subject: [PATCH 16/35] Do not overwrite existing headers in applyFetchOptions --- scm-ui/ui-components/src/apiclient.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 4c1bad8ac1..473bf3b925 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -27,13 +27,17 @@ const extractXsrfToken = () => { }; const applyFetchOptions: (p: RequestInit) => RequestInit = o => { - const headers: { [key: string]: string } = { - Cache: "no-cache", - // identify the request as ajax request - "X-Requested-With": "XMLHttpRequest", - // identify the web interface - "X-SCM-Client": "WUI" - }; + if (!o.headers) { + o.headers = {}; + } + + // @ts-ignore We are sure that here we only get headers of type Record + const headers: Record = o.headers; + headers["Cache"] = "no-cache"; + // identify the request as ajax request + headers["X-Requested-With"] = "XMLHttpRequest"; + // identify the web interface + headers["X-SCM-Client"] = "WUI"; const xsrf = extractXsrfToken(); if (xsrf) { @@ -159,7 +163,7 @@ class ApiClient { options = applyFetchOptions(options); if (contentType) { if (!options.headers) { - options.headers = new Headers(); + options.headers = {}; } // @ts-ignore We are sure that here we only get headers of type Record options.headers["Content-Type"] = contentType; From 4f43bce90eef4986ecc3a74ed2fb9e092df1eccf Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 20 Nov 2019 11:14:58 +0100 Subject: [PATCH 17/35] Reduce visibility of misleading install function This install function might be mistaken to really install this plugin, whereas it only sets its internal status and does not really do smth. --- scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java b/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java index db8d96ca15..c05e970e50 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java +++ b/scm-core/src/main/java/sonia/scm/plugin/AvailablePlugin.java @@ -25,7 +25,7 @@ public class AvailablePlugin implements Plugin { return pending; } - public AvailablePlugin install() { + AvailablePlugin install() { Preconditions.checkState(!pending, "installation is already pending"); return new AvailablePlugin(pluginDescriptor, true); } From e0fa0d09d943852f47721d347dd3cab3074af957 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 20 Nov 2019 11:15:36 +0100 Subject: [PATCH 18/35] resolve fetch-loop for users and groups --- scm-ui/ui-webapp/src/groups/containers/Groups.tsx | 11 ++++++++++- scm-ui/ui-webapp/src/users/containers/Users.tsx | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/scm-ui/ui-webapp/src/groups/containers/Groups.tsx b/scm-ui/ui-webapp/src/groups/containers/Groups.tsx index 23defa8e49..4661eb57a5 100644 --- a/scm-ui/ui-webapp/src/groups/containers/Groups.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/Groups.tsx @@ -49,13 +49,22 @@ class Groups extends React.Component { componentDidUpdate = (prevProps: Props) => { const { loading, list, page, groupLink, location, fetchGroupsByPage } = this.props; if (list && page && !loading) { - const statePage: number = list.page + 1; + const statePage: number = this.resolveStatePage(); if (page !== statePage || prevProps.location.search !== location.search) { fetchGroupsByPage(groupLink, page, urls.getQueryStringFromLocation(location)); } } }; + resolveStatePage = () => { + const { list } = this.props; + if (list.page) { + return list.page + 1; + } + // set page to 1 if undefined, because if groups couldn't be fetched it would lead to an fetch-loop otherwise + return 1; + }; + render() { const { groups, loading, error, canAddGroups, t } = this.props; return ( diff --git a/scm-ui/ui-webapp/src/users/containers/Users.tsx b/scm-ui/ui-webapp/src/users/containers/Users.tsx index bb97eab1b4..d634ed5ca7 100644 --- a/scm-ui/ui-webapp/src/users/containers/Users.tsx +++ b/scm-ui/ui-webapp/src/users/containers/Users.tsx @@ -49,13 +49,22 @@ class Users extends React.Component { componentDidUpdate = (prevProps: Props) => { const { loading, list, page, usersLink, location, fetchUsersByPage } = this.props; if (list && page && !loading) { - const statePage: number = list.page + 1; + const statePage: number = this.resolveStatePage(); if (page !== statePage || prevProps.location.search !== location.search) { fetchUsersByPage(usersLink, page, urls.getQueryStringFromLocation(location)); } } }; + resolveStatePage = (props = this.props) => { + const { list } = props; + if (list.page) { + return list.page + 1; + } + // set page to 1 if undefined, because if users couldn't be fetched it would lead to a fetch-loop otherwise + return 1; + }; + render() { const { users, loading, error, canAddUsers, t } = this.props; return ( From 906c27ebba86e4ef622edb9e5708fabd9af1001b Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 20 Nov 2019 11:42:32 +0100 Subject: [PATCH 19/35] create scmadmin also if only _anonymous user exists --- .../sonia/scm/lifecycle/SetupContextListener.java | 10 +++++++--- .../scm/lifecycle/SetupContextListenerTest.java | 14 +++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java index 512a4fc534..07e7d0409b 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java @@ -61,7 +61,7 @@ public class SetupContextListener implements ServletContextListener { @Override public void run() { - if (isFirstStart()) { + if (shouldCreateAdminAccount()) { createAdminAccount(); } if (anonymousUserRequiredButNotExists()) { @@ -73,8 +73,12 @@ public class SetupContextListener implements ServletContextListener { return scmConfiguration.isAnonymousAccessEnabled() && !userManager.contains(SCMContext.USER_ANONYMOUS); } - private boolean isFirstStart() { - return userManager.getAll().isEmpty(); + private boolean shouldCreateAdminAccount() { + return userManager.getAll().isEmpty() || onlyAnonymousUserExists(); + } + + private boolean onlyAnonymousUserExists() { + return userManager.getAll().size() == 1 && userManager.contains(SCMContext.USER_ANONYMOUS); } private void createAdminAccount() { diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java index 27ddd42cc1..efacbd3594 100644 --- a/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java @@ -70,7 +70,19 @@ class SetupContextListenerTest { } @Test - void shouldCreateAdminAccountAndAssignPermissions() { + void shouldCreateAdminAccountIfNoUserExistsAndAssignPermissions() { + when(passwordService.encryptPassword("scmadmin")).thenReturn("secret"); + + setupContextListener.contextInitialized(null); + + verifyAdminCreated(); + verifyAdminPermissionsAssigned(); + } + + @Test + void shouldCreateAdminAccountIfOnlyAnonymousUserExistsAndAssignPermissions() { + when(userManager.getAll()).thenReturn(Lists.newArrayList(SCMContext.ANONYMOUS)); + when(userManager.contains(SCMContext.USER_ANONYMOUS)).thenReturn(true); when(passwordService.encryptPassword("scmadmin")).thenReturn("secret"); setupContextListener.contextInitialized(null); From 5de7d5df94c41b2db572c048f97e1147c4d26674 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 20 Nov 2019 13:40:40 +0100 Subject: [PATCH 20/35] show notification if diff is empty --- scm-ui/ui-components/src/repos/Diff.tsx | 23 +++++++++++-------- .../ui-components/src/repos/LoadingDiff.tsx | 1 + .../main/resources/locales/de/plugins.json | 3 ++- .../main/resources/locales/en/plugins.json | 9 ++++---- .../main/resources/locales/es/plugins.json | 3 ++- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/scm-ui/ui-components/src/repos/Diff.tsx b/scm-ui/ui-components/src/repos/Diff.tsx index cb4ad30f9f..b34c5ae68e 100644 --- a/scm-ui/ui-components/src/repos/Diff.tsx +++ b/scm-ui/ui-components/src/repos/Diff.tsx @@ -1,11 +1,14 @@ import React from "react"; import DiffFile from "./DiffFile"; import { DiffObjectProps, File } from "./DiffTypes"; +import Notification from "../Notification"; +import { WithTranslation, withTranslation } from "react-i18next"; -type Props = DiffObjectProps & { - diff: File[]; - defaultCollapse?: boolean; -}; +type Props = WithTranslation & + DiffObjectProps & { + diff: File[]; + defaultCollapse?: boolean; + }; class Diff extends React.Component { static defaultProps: Partial = { @@ -13,15 +16,17 @@ class Diff extends React.Component { }; render() { - const { diff, ...fileProps } = this.props; + const { diff, t, ...fileProps } = this.props; return ( <> - {diff.map((file, index) => ( - - ))} + {diff.length === 0 ? ( + {t("noDiffFound")} + ) : ( + diff.map((file, index) => ) + )} ); } } -export default Diff; +export default withTranslation("plugins")(Diff); diff --git a/scm-ui/ui-components/src/repos/LoadingDiff.tsx b/scm-ui/ui-components/src/repos/LoadingDiff.tsx index 1cfbc00b14..45b62f9b35 100644 --- a/scm-ui/ui-components/src/repos/LoadingDiff.tsx +++ b/scm-ui/ui-components/src/repos/LoadingDiff.tsx @@ -43,6 +43,7 @@ class LoadingDiff extends React.Component { fetchDiff = () => { const { url } = this.props; + this.setState({loading: true}); apiClient .get(url) .then(response => response.text()) diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index 3cb6522922..a2cd4530dd 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -202,5 +202,6 @@ "CustomNamespaceStrategy": "Benutzerdefiniert", "CurrentYearNamespaceStrategy": "Aktuelles Jahr", "RepositoryTypeNamespaceStrategy": "Repository Typ" - } + }, + "noDiffFound": "Kein Diff zwischen den ausgewählten Branches gefunden." } diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index 0be1c1f803..271d7e8c20 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -198,9 +198,10 @@ } }, "namespaceStrategies": { - "UsernameNamespaceStrategy": "Username", - "CustomNamespaceStrategy": "Custom", - "CurrentYearNamespaceStrategy": "Current year", + "UsernameNamespaceStrategy": "Username", + "CustomNamespaceStrategy": "Custom", + "CurrentYearNamespaceStrategy": "Current year", "RepositoryTypeNamespaceStrategy": "Repository type" - } + }, + "noDiffFound": "No Diff between the selected branches found." } diff --git a/scm-webapp/src/main/resources/locales/es/plugins.json b/scm-webapp/src/main/resources/locales/es/plugins.json index e56973946b..042a6ebc63 100644 --- a/scm-webapp/src/main/resources/locales/es/plugins.json +++ b/scm-webapp/src/main/resources/locales/es/plugins.json @@ -182,5 +182,6 @@ "CustomNamespaceStrategy": "Personalizar", "CurrentYearNamespaceStrategy": "Año actual", "RepositoryTypeNamespaceStrategy": "Tipo de repositorio" - } + }, + "noDiffFound": "No se encontraron diferencias entre las ramas seleccionadas." } From d962265d8dc0423c591f428948bb91e8b467da8a Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 20 Nov 2019 14:47:47 +0100 Subject: [PATCH 21/35] Correct order of exports Without this `yarn run serve` will run on port 8080 serving the scss --- scm-ui/ui-scripts/src/webpack.config.js | 70 ++++++++++++------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/scm-ui/ui-scripts/src/webpack.config.js b/scm-ui/ui-scripts/src/webpack.config.js index 639872c6b8..2dd5b4c470 100644 --- a/scm-ui/ui-scripts/src/webpack.config.js +++ b/scm-ui/ui-scripts/src/webpack.config.js @@ -7,41 +7,6 @@ const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); const root = path.resolve(process.cwd(), "scm-ui"); module.exports = [ - { - context: root, - entry: "./ui-styles/src/scm.scss", - module: { - rules: [ - { - test: /\.(css|scss|sass)$/i, - use: [ - { - loader: MiniCssExtractPlugin.loader - }, - "css-loader", - "sass-loader" - ] - }, - { - test: /\.(png|svg|jpg|gif|woff2?|eot|ttf)$/, - use: ["file-loader"] - } - ] - }, - plugins: [ - new MiniCssExtractPlugin({ - filename: "ui-styles.css", - ignoreOrder: false - }) - ], - optimization: { - minimizer: [new OptimizeCSSAssetsPlugin({})] - }, - output: { - path: path.join(root, "target", "assets"), - filename: "ui-styles.bundle.js" - } - }, { context: root, entry: { @@ -142,6 +107,41 @@ module.exports = [ } } }, + { + context: root, + entry: "./ui-styles/src/scm.scss", + module: { + rules: [ + { + test: /\.(css|scss|sass)$/i, + use: [ + { + loader: MiniCssExtractPlugin.loader + }, + "css-loader", + "sass-loader" + ] + }, + { + test: /\.(png|svg|jpg|gif|woff2?|eot|ttf)$/, + use: ["file-loader"] + } + ] + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: "ui-styles.css", + ignoreOrder: false + }) + ], + optimization: { + minimizer: [new OptimizeCSSAssetsPlugin({})] + }, + output: { + path: path.join(root, "target", "assets"), + filename: "ui-styles.bundle.js" + } + }, { context: path.resolve(root), entry: { From ba8c2f4ce734122132ed581daa9412e3d40de012 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 20 Nov 2019 15:23:10 +0000 Subject: [PATCH 22/35] Close branch feature/add_optional_headers_in_apiclient From 793233146b549ac92849dedd5abdeb1572f461fa Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 20 Nov 2019 15:37:42 +0000 Subject: [PATCH 23/35] Close branch bugfix/new_pr_undefined_on_diff From 67c21b18e32f456d269c939526e0ddff33d93275 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Wed, 20 Nov 2019 16:50:18 +0100 Subject: [PATCH 24/35] Move translation key to correct file --- scm-ui/ui-components/src/repos/Diff.tsx | 4 ++-- scm-ui/ui-webapp/public/locales/de/repos.json | 3 ++- scm-ui/ui-webapp/public/locales/en/repos.json | 3 ++- scm-ui/ui-webapp/public/locales/es/repos.json | 3 ++- scm-webapp/src/main/resources/locales/de/plugins.json | 3 +-- scm-webapp/src/main/resources/locales/en/plugins.json | 3 +-- scm-webapp/src/main/resources/locales/es/plugins.json | 3 +-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scm-ui/ui-components/src/repos/Diff.tsx b/scm-ui/ui-components/src/repos/Diff.tsx index b34c5ae68e..d7123903a3 100644 --- a/scm-ui/ui-components/src/repos/Diff.tsx +++ b/scm-ui/ui-components/src/repos/Diff.tsx @@ -20,7 +20,7 @@ class Diff extends React.Component { return ( <> {diff.length === 0 ? ( - {t("noDiffFound")} + {t("diff.noDiffFound")} ) : ( diff.map((file, index) => ) )} @@ -29,4 +29,4 @@ class Diff extends React.Component { } } -export default withTranslation("plugins")(Diff); +export default withTranslation("repos")(Diff); diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json index e156cc903e..4dfba695ab 100644 --- a/scm-ui/ui-webapp/public/locales/de/repos.json +++ b/scm-ui/ui-webapp/public/locales/de/repos.json @@ -177,7 +177,8 @@ }, "diff": { "sideBySide": "Zweispaltig", - "combined": "Kombiniert" + "combined": "Kombiniert", + "noDiffFound": "Kein Diff zwischen den ausgewählten Branches gefunden." }, "fileUpload": { "clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.", diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json index 0466bab77f..e2ccf8326e 100644 --- a/scm-ui/ui-webapp/public/locales/en/repos.json +++ b/scm-ui/ui-webapp/public/locales/en/repos.json @@ -184,7 +184,8 @@ "copy": "copied" }, "sideBySide": "side-by-side", - "combined": "combined" + "combined": "combined", + "noDiffFound": "No Diff between the selected branches found." }, "fileUpload": { "clickHere": "Click here to select your file", diff --git a/scm-ui/ui-webapp/public/locales/es/repos.json b/scm-ui/ui-webapp/public/locales/es/repos.json index ef9bd2badc..c3bf57c630 100644 --- a/scm-ui/ui-webapp/public/locales/es/repos.json +++ b/scm-ui/ui-webapp/public/locales/es/repos.json @@ -184,7 +184,8 @@ "copy": "copiado" }, "sideBySide": "dos columnas", - "combined": "combinado" + "combined": "combinado", + "noDiffFound": "No se encontraron diferencias entre las ramas seleccionadas." }, "fileUpload": { "clickHere": "Haga click aquí para seleccionar su fichero", diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index a2cd4530dd..3cb6522922 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -202,6 +202,5 @@ "CustomNamespaceStrategy": "Benutzerdefiniert", "CurrentYearNamespaceStrategy": "Aktuelles Jahr", "RepositoryTypeNamespaceStrategy": "Repository Typ" - }, - "noDiffFound": "Kein Diff zwischen den ausgewählten Branches gefunden." + } } diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index 271d7e8c20..83b463b8b6 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -202,6 +202,5 @@ "CustomNamespaceStrategy": "Custom", "CurrentYearNamespaceStrategy": "Current year", "RepositoryTypeNamespaceStrategy": "Repository type" - }, - "noDiffFound": "No Diff between the selected branches found." + } } diff --git a/scm-webapp/src/main/resources/locales/es/plugins.json b/scm-webapp/src/main/resources/locales/es/plugins.json index 042a6ebc63..e56973946b 100644 --- a/scm-webapp/src/main/resources/locales/es/plugins.json +++ b/scm-webapp/src/main/resources/locales/es/plugins.json @@ -182,6 +182,5 @@ "CustomNamespaceStrategy": "Personalizar", "CurrentYearNamespaceStrategy": "Año actual", "RepositoryTypeNamespaceStrategy": "Tipo de repositorio" - }, - "noDiffFound": "No se encontraron diferencias entre las ramas seleccionadas." + } } From 0144d4cc7d37f91ef2c268d1a88b980d20c8037b Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 21 Nov 2019 10:05:47 +0000 Subject: [PATCH 25/35] Close branch feature/public_flag_migration From 83e50156ded29df243adc34edf85959ba08a07d9 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 21 Nov 2019 10:18:30 +0000 Subject: [PATCH 26/35] Close branch bugfix/fetch_loop From 9cf55b83356700340c8821bdccbe6de272ddd1ab Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 21 Nov 2019 13:06:22 +0100 Subject: [PATCH 27/35] Use api client for auto complete --- scm-ui/ui-components/src/UserGroupAutocomplete.tsx | 4 +++- scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx | 4 +++- scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/scm-ui/ui-components/src/UserGroupAutocomplete.tsx b/scm-ui/ui-components/src/UserGroupAutocomplete.tsx index 5c047b9353..f4e445eba9 100644 --- a/scm-ui/ui-components/src/UserGroupAutocomplete.tsx +++ b/scm-ui/ui-components/src/UserGroupAutocomplete.tsx @@ -1,6 +1,7 @@ import React from "react"; import { SelectValue, AutocompleteObject } from "@scm-manager/ui-types"; import Autocomplete from "./Autocomplete"; +import { apiClient } from "./apiclient"; export type AutocompleteProps = { autocompleteLink?: string; @@ -19,7 +20,8 @@ export default class UserGroupAutocomplete extends React.Component { loadSuggestions = (inputValue: string): Promise => { const url = this.props.autocompleteLink; const link = url + "?q="; - return fetch(link + inputValue) + return apiClient + .get(link + inputValue) .then(response => response.json()) .then((json: AutocompleteObject[]) => { return json.map(element => { diff --git a/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx index 5d50de99cf..0a1ee6bf6b 100644 --- a/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx @@ -7,6 +7,7 @@ import { Page } from "@scm-manager/ui-components"; import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource"; import { createGroup, isCreateGroupPending, getCreateGroupFailure, createGroupReset } from "../modules/groups"; import GroupForm from "../components/GroupForm"; +import { apiClient } from "@scm-manager/ui-components/src"; type Props = WithTranslation & { createGroup: (link: string, group: Group, callback?: () => void) => void; @@ -40,7 +41,8 @@ class CreateGroup extends React.Component { loadUserAutocompletion = (inputValue: string) => { const url = this.props.autocompleteLink + "?q="; - return fetch(url + inputValue) + return apiClient + .get(url + inputValue) .then(response => response.json()) .then(json => { return json.map(element => { diff --git a/scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx index 9a70bfefa4..e3307274bd 100644 --- a/scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx @@ -8,6 +8,7 @@ import { Group } from "@scm-manager/ui-types"; import { ErrorNotification } from "@scm-manager/ui-components"; import { getUserAutoCompleteLink } from "../../modules/indexResource"; import DeleteGroup from "./DeleteGroup"; +import { apiClient } from "@scm-manager/ui-components/src"; type Props = { group: Group; @@ -36,7 +37,8 @@ class EditGroup extends React.Component { loadUserAutocompletion = (inputValue: string) => { const url = this.props.autocompleteLink + "?q="; - return fetch(url + inputValue) + return apiClient + .get(url + inputValue) .then(response => response.json()) .then(json => { return json.map(element => { From 80545405442f97d369bd7346b722869767918c3a Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 21 Nov 2019 16:38:34 +0100 Subject: [PATCH 28/35] Extend timeout on restart after plugin installation --- scm-ui/ui-webapp/src/admin/plugins/components/waitForRestart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/waitForRestart.ts b/scm-ui/ui-webapp/src/admin/plugins/components/waitForRestart.ts index 48fbc8002c..77e7520f17 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/waitForRestart.ts +++ b/scm-ui/ui-webapp/src/admin/plugins/components/waitForRestart.ts @@ -1,7 +1,7 @@ import { apiClient } from "@scm-manager/ui-components"; const waitForRestart = () => { - const endTime = Number(new Date()) + 10000; + const endTime = Number(new Date()) + 60000; let started = false; const executor = (resolve, reject) => { From aedf34c5817b9b83bc9ef5caa313b2ad6c1d2aef Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 22 Nov 2019 15:43:58 +0100 Subject: [PATCH 29/35] Postpone restart after plugin (de)installation --- .../main/java/sonia/scm/plugin/DefaultPluginManager.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index 07accbcc76..0c931ff7a6 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -243,7 +243,13 @@ public class DefaultPluginManager implements PluginManager { } private void restart(String cause) { - eventBus.post(new RestartEvent(PluginManager.class, cause)); + new Thread(() -> { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + eventBus.post(new RestartEvent(PluginManager.class, cause)); + }).start(); } private void cancelPending(List pendingInstallations) { From 450ed7bd4eb10518640d834814800ab08e36b065 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 25 Nov 2019 08:15:13 +0100 Subject: [PATCH 30/35] Fix unit test --- .../scm/plugin/DefaultPluginManager.java | 9 +++--- .../scm/plugin/DefaultPluginManagerTest.java | 31 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index 0c931ff7a6..b74b17cd88 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -187,7 +187,7 @@ public class DefaultPluginManager implements PluginManager { if (!pendingInstallations.isEmpty()) { if (restartAfterInstallation) { - restart("plugin installation"); + triggerRestart("plugin installation"); } else { pendingInstallQueue.addAll(pendingInstallations); updateMayUninstallFlag(); @@ -205,7 +205,7 @@ public class DefaultPluginManager implements PluginManager { markForUninstall(installed); if (restartAfterInstallation) { - restart("plugin installation"); + triggerRestart("plugin installation"); } else { updateMayUninstallFlag(); } @@ -238,11 +238,12 @@ public class DefaultPluginManager implements PluginManager { public void executePendingAndRestart() { PluginPermissions.manage().check(); if (!pendingInstallQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) { - restart("execute pending plugin changes"); + triggerRestart("execute pending plugin changes"); } } - private void restart(String cause) { + @VisibleForTesting + void triggerRestart(String cause) { new Thread(() -> { try { Thread.sleep(200); diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java index f817476848..f6aa1d7301 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -12,13 +12,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junitpioneer.jupiter.TempDirectory; import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.NotFoundException; import sonia.scm.ScmConstraintViolationException; -import sonia.scm.event.ScmEventBus; -import sonia.scm.lifecycle.RestartEvent; import java.io.IOException; import java.nio.file.Files; @@ -48,9 +45,6 @@ import static sonia.scm.plugin.PluginTestHelper.createInstalled; @ExtendWith(TempDirectory.class) class DefaultPluginManagerTest { - @Mock - private ScmEventBus eventBus; - @Mock private PluginLoader loader; @@ -60,12 +54,13 @@ class DefaultPluginManagerTest { @Mock private PluginInstaller installer; - @InjectMocks private DefaultPluginManager manager; @Mock private Subject subject; + private boolean restartTriggered = false; + @BeforeEach void mockInstaller() { lenient().when(installer.install(any())).then(ic -> { @@ -74,6 +69,16 @@ class DefaultPluginManagerTest { }); } + @BeforeEach + void createPluginManagerToTestWithCapturedRestart() { + manager = new DefaultPluginManager(null, loader, center, installer) { // event bus is only used in restart and this is replaced here + @Override + void triggerRestart(String cause) { + restartTriggered = true; + } + }; + } + @Nested class WithAdminPermissions { @@ -180,7 +185,7 @@ class DefaultPluginManagerTest { manager.install("scm-git-plugin", false); verify(installer).install(git); - verify(eventBus, never()).post(any()); + assertThat(restartTriggered).isFalse(); } @Test @@ -258,7 +263,7 @@ class DefaultPluginManagerTest { manager.install("scm-git-plugin", true); verify(installer).install(git); - verify(eventBus).post(any(RestartEvent.class)); + assertThat(restartTriggered).isTrue(); } @Test @@ -267,7 +272,7 @@ class DefaultPluginManagerTest { when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(gitInstalled)); manager.install("scm-git-plugin", true); - verify(eventBus, never()).post(any()); + assertThat(restartTriggered).isFalse(); } @Test @@ -289,14 +294,14 @@ class DefaultPluginManagerTest { manager.install("scm-review-plugin", false); manager.executePendingAndRestart(); - verify(eventBus).post(any(RestartEvent.class)); + assertThat(restartTriggered).isTrue(); } @Test void shouldNotSendRestartEventWithoutPendingPlugins() { manager.executePendingAndRestart(); - verify(eventBus, never()).post(any()); + assertThat(restartTriggered).isFalse(); } @Test @@ -447,7 +452,7 @@ class DefaultPluginManagerTest { manager.executePendingAndRestart(); - verify(eventBus).post(any(RestartEvent.class)); + assertThat(restartTriggered).isTrue(); } @Test From fdef95f254454145af7cae1b245aabe5fea0ec6e Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 25 Nov 2019 07:43:39 +0000 Subject: [PATCH 31/35] Close branch bugfix/autocomplete_with_xsrf From f2d92c49d1518a29dce20eea6acea9fa916ec298 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 25 Nov 2019 11:37:23 +0100 Subject: [PATCH 32/35] show error messages on failed branch creation --- .../ui-webapp/src/repos/branches/containers/CreateBranch.tsx | 2 +- scm-ui/ui-webapp/src/repos/branches/modules/branches.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-ui/ui-webapp/src/repos/branches/containers/CreateBranch.tsx b/scm-ui/ui-webapp/src/repos/branches/containers/CreateBranch.tsx index 8ae8640c62..36fe6746e3 100644 --- a/scm-ui/ui-webapp/src/repos/branches/containers/CreateBranch.tsx +++ b/scm-ui/ui-webapp/src/repos/branches/containers/CreateBranch.tsx @@ -114,7 +114,7 @@ const mapDispatchToProps = dispatch => { const mapStateToProps = (state, ownProps) => { const { repository } = ownProps; const loading = isFetchBranchesPending(state, repository) || isCreateBranchPending(state, repository); - const error = getFetchBranchesFailure(state, repository) || getCreateBranchFailure(state); + const error = getFetchBranchesFailure(state, repository) || getCreateBranchFailure(state, repository); const branches = getBranches(state, repository); const createBranchesLink = getBranchCreateLink(state, repository); return { diff --git a/scm-ui/ui-webapp/src/repos/branches/modules/branches.ts b/scm-ui/ui-webapp/src/repos/branches/modules/branches.ts index 35c55aec42..a58f43c4c7 100644 --- a/scm-ui/ui-webapp/src/repos/branches/modules/branches.ts +++ b/scm-ui/ui-webapp/src/repos/branches/modules/branches.ts @@ -186,8 +186,8 @@ export function isCreateBranchPending(state: object, repository: Repository) { return isPending(state, CREATE_BRANCH, createKey(repository)); } -export function getCreateBranchFailure(state: object) { - return getFailure(state, CREATE_BRANCH); +export function getCreateBranchFailure(state: object, repository: Repository) { + return getFailure(state, CREATE_BRANCH, createKey(repository)); } export function createBranchPending(repository: Repository): Action { From 47fc883cab43f74f81361865014ba5b3c2cdd3e7 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 25 Nov 2019 12:24:54 +0100 Subject: [PATCH 33/35] fix unit tests --- .../ui-webapp/src/repos/branches/modules/branches.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-ui/ui-webapp/src/repos/branches/modules/branches.test.ts b/scm-ui/ui-webapp/src/repos/branches/modules/branches.test.ts index 2003477101..b23ccc640b 100644 --- a/scm-ui/ui-webapp/src/repos/branches/modules/branches.test.ts +++ b/scm-ui/ui-webapp/src/repos/branches/modules/branches.test.ts @@ -482,14 +482,14 @@ describe("branches", () => { it("should return error when create branch did fail", () => { const state = { failure: { - [CREATE_BRANCH]: error + [CREATE_BRANCH + `/${repository.namespace}/${repository.name}`]: error } }; - expect(getCreateBranchFailure(state)).toEqual(error); + expect(getCreateBranchFailure(state, repository)).toEqual(error); }); it("should return undefined when create branch did not fail", () => { - expect(getCreateBranchFailure({})).toBe(undefined); + expect(getCreateBranchFailure({}, repository)).toBe(undefined); }); }); }); From 34ab9104f21cc9c6f11e20fc01203a13dfa18d46 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 25 Nov 2019 12:12:47 +0000 Subject: [PATCH 34/35] Close branch bugfix/branch_error_messages From 04c004fedee0ad16c7d0805cf28c1a56c869f015 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Mon, 25 Nov 2019 12:25:10 +0000 Subject: [PATCH 35/35] Close branch feature/postpone_restart_for_plugin