From 59c0b152f5b3e342bdedaafebfaa41bbe4fd69e1 Mon Sep 17 00:00:00 2001
From: Eduard Heimbuch
Date: Tue, 23 Jun 2020 16:07:38 +0200
Subject: [PATCH 01/20] add rest endpoint for renaming repository name and
namespace
---
.../java/sonia/scm/repository/Repository.java | 2 +-
.../api/v2/resources/RepositoryRenameDto.java | 39 +++++++++
.../api/v2/resources/RepositoryResource.java | 85 ++++++++++++++++---
.../RepositoryToRepositoryDtoMapper.java | 12 ++-
.../scm/api/v2/resources/ResourceLinks.java | 4 +
.../META-INF/scm/repository-permissions.xml | 1 +
.../resources/RepositoryRootResourceTest.java | 66 ++++++++++++++
.../api/v2/resources/RepositoryTestBase.java | 6 +-
.../RepositoryToRepositoryDtoMapperTest.java | 21 +++++
.../sonia/scm/api/v2/rename-repo.json | 4 +
10 files changed, 226 insertions(+), 14 deletions(-)
create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRenameDto.java
create mode 100644 scm-webapp/src/test/resources/sonia/scm/api/v2/rename-repo.json
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 b1de749cdf..bd02021ed6 100644
--- a/scm-core/src/main/java/sonia/scm/repository/Repository.java
+++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java
@@ -53,7 +53,7 @@ import java.util.Set;
*/
@StaticPermissions(
value = "repository",
- permissions = {"read", "modify", "delete", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"},
+ permissions = {"read", "modify", "delete", "rename", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"},
custom = true, customGlobal = true
)
@XmlAccessorType(XmlAccessType.FIELD)
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRenameDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRenameDto.java
new file mode 100644
index 0000000000..67848db3fe
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRenameDto.java
@@ -0,0 +1,39 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package sonia.scm.api.v2.resources;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import sonia.scm.util.ValidationUtil;
+
+import javax.validation.constraints.Pattern;
+
+@Getter
+@NoArgsConstructor
+public class RepositoryRenameDto {
+ @Pattern(regexp = ValidationUtil.REGEX_REPOSITORYNAME)
+ private String name;
+ private String namespace;
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
index 2ac4f0674f..a658973257 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
@@ -24,21 +24,24 @@
package sonia.scm.api.v2.resources;
+import com.google.common.base.Strings;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
+import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
-import javax.inject.Provider;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -59,18 +62,20 @@ public class RepositoryResource {
private final RepositoryManager manager;
private final SingleResourceManagerAdapter adapter;
private final RepositoryBasedResourceProvider resourceProvider;
+ private final ScmConfiguration scmConfiguration;
@Inject
public RepositoryResource(
RepositoryToRepositoryDtoMapper repositoryToDtoMapper,
RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, RepositoryManager manager,
- RepositoryBasedResourceProvider resourceProvider
- ) {
+ RepositoryBasedResourceProvider resourceProvider,
+ ScmConfiguration scmConfiguration) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper;
this.adapter = new SingleResourceManagerAdapter<>(manager, Repository.class);
this.resourceProvider = resourceProvider;
+ this.scmConfiguration = scmConfiguration;
}
/**
@@ -79,8 +84,7 @@ public class RepositoryResource {
* Note: This method requires "repository" privilege.
*
* @param namespace the namespace of the repository
- * @param name the name of the repository
- *
+ * @param name the name of the repository
*/
@GET
@Path("")
@@ -118,7 +122,7 @@ public class RepositoryResource {
schema = @Schema(implementation = ErrorDto.class)
)
)
- public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name){
+ public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name) {
return adapter.get(loadBy(namespace, name), repositoryToDtoMapper::map);
}
@@ -128,8 +132,7 @@ public class RepositoryResource {
* Note: This method requires "repository" privilege.
*
* @param namespace the namespace of the repository to delete
- * @param name the name of the repository to delete
- *
+ * @param name the name of the repository to delete
*/
@DELETE
@Path("")
@@ -147,8 +150,8 @@ public class RepositoryResource {
*
* Note: This method requires "repository" privilege.
*
- * @param namespace the namespace of the repository to be modified
- * @param name the name of the repository to be modified
+ * @param namespace the namespace of the repository to be modified
+ * @param name the name of the repository to be modified
* @param repository repository object to modify
*/
@PUT
@@ -176,6 +179,68 @@ public class RepositoryResource {
);
}
+ /**
+ * Renames the given repository.
+ *
+ * Note: This method requires "repository" privilege.
+ *
+ * @param namespace the namespace of the repository to be modified
+ * @param name the name of the repository to be modified
+ * @param renameDto renameDto object to modify
+ */
+ @POST
+ @Path("rename")
+ @Consumes(VndMediaType.REPOSITORY)
+ @Operation(summary = "Rename repository", description = "Renames the repository for the given namespace and name.", tags = "Repository")
+ @ApiResponse(responseCode = "204", description = "update success")
+ @ApiResponse(responseCode = "400", description = "invalid body, e.g. illegal change of namespace or name")
+ @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
+ @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository:renameDto\" privilege")
+ @ApiResponse(
+ responseCode = "404",
+ description = "not found, no repository with the specified namespace and name available",
+ content = @Content(
+ mediaType = VndMediaType.ERROR_TYPE,
+ schema = @Schema(implementation = ErrorDto.class)
+ ))
+ @ApiResponse(responseCode = "500", description = "internal server error")
+ public Response rename(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryRenameDto renameDto) {
+ Repository repo = loadBy(namespace, name).get();
+
+ if (isRenameForbidden(repo)) {
+ return Response.status(403).build();
+ }
+
+ if (hasNamespaceOrNameNotChanged(repo, renameDto)) {
+ return Response.status(400).build();
+ }
+
+ if (!Strings.isNullOrEmpty(renameDto.getName())) {
+ repo.setName(renameDto.getName());
+ }
+ if (!Strings.isNullOrEmpty(renameDto.getNamespace())) {
+ repo.setNamespace(renameDto.getNamespace());
+ }
+
+ return adapter.update(
+ loadBy(namespace, name),
+ existing -> repo,
+ changed -> true,
+ r -> r.getNamespaceAndName().logString()
+ );
+ }
+
+ private boolean hasNamespaceOrNameNotChanged(Repository repo, @Valid RepositoryRenameDto renameDto) {
+ return repo.getName().equals(renameDto.getName())
+ && repo.getNamespace().equals(renameDto.getNamespace());
+ }
+
+ private boolean isRenameForbidden(Repository repo) {
+ return !scmConfiguration.getNamespaceStrategy().equals("CustomNamespaceStrategy")
+ || !RepositoryPermissions.rename(repo).isPermitted();
+ }
+
+
private Repository processUpdate(RepositoryDto repositoryDto, Repository existing) {
Repository changedRepository = dtoToRepositoryMapper.map(repositoryDto, existing.getId());
changedRepository.setPermissions(existing.getPermissions());
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java
index 8dd3e8fb1b..3de24d64a7 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java
@@ -24,12 +24,12 @@
package sonia.scm.api.v2.resources;
-import com.google.inject.Inject;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links;
import org.mapstruct.Mapper;
import org.mapstruct.ObjectFactory;
+import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.Feature;
import sonia.scm.repository.HealthCheckFailure;
import sonia.scm.repository.Repository;
@@ -41,6 +41,7 @@ import sonia.scm.repository.api.ScmProtocol;
import sonia.scm.web.EdisonHalAppender;
import sonia.scm.web.api.RepositoryToHalMapper;
+import javax.inject.Inject;
import java.util.List;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
@@ -56,6 +57,8 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapperread
modify
delete
+ rename
pull
push
permissionRead
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
index bcc1d2bc3f..81f712b728 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
@@ -35,11 +35,13 @@ import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.jupiter.api.Nested;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.PageResult;
+import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryInitializer;
@@ -62,6 +64,7 @@ import static java.util.Collections.singletonList;
import static java.util.stream.Stream.of;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
@@ -103,6 +106,8 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
private ScmPathInfo uriInfo;
@Mock
private RepositoryInitializer repositoryInitializer;
+ @Mock
+ private ScmConfiguration scmConfiguration;
@Captor
private ArgumentCaptor> filterCaptor;
@@ -121,11 +126,13 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
super.repositoryToDtoMapper = repositoryToDtoMapper;
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
super.manager = repositoryManager;
+ super.scmConfiguration = scmConfiguration;
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(scmPathInfoStore.get()).thenReturn(uriInfo);
+ when(scmConfiguration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
trillian.add(new User("trillian"), REALM);
@@ -372,6 +379,65 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
assertTrue(response.getContentAsString().contains("\"protocol\":[{\"href\":\"http://\",\"name\":\"http\"},{\"href\":\"ssh://\",\"name\":\"ssh\"}]"));
}
+ @Test
+ public void shouldNotRenameRepositoryIfNamespaceStrategyIsNotCustom() throws Exception {
+ mockRepository("space", "repo");
+ when(scmConfiguration.getNamespaceStrategy()).thenReturn("UsernameNamespaceStrategy");
+
+ URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
+ byte[] repository = Resources.toByteArray(url);
+
+ MockHttpRequest request = MockHttpRequest
+ .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
+ .contentType(VndMediaType.REPOSITORY)
+ .content(repository);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertEquals(SC_FORBIDDEN, response.getStatus());
+ }
+
+ @Test
+ public void shouldNotRenameRepositoryIfNamespaceAndNameDidNotChanged() throws Exception {
+ mockRepository("space", "x");
+ when(scmConfiguration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
+
+ URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
+ byte[] repository = Resources.toByteArray(url);
+
+ MockHttpRequest request = MockHttpRequest
+ .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/x/rename")
+ .contentType(VndMediaType.REPOSITORY)
+ .content(repository);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertEquals(SC_BAD_REQUEST, response.getStatus());
+ }
+
+ @Test
+ public void shouldRenameRepository() throws Exception {
+ mockRepository("space", "repo");
+ when(scmConfiguration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
+
+ URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
+ byte[] repository = Resources.toByteArray(url);
+
+ MockHttpRequest request = MockHttpRequest
+ .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
+ .contentType(VndMediaType.REPOSITORY)
+ .content(repository);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
+ assertEquals(SC_NO_CONTENT, response.getStatus());
+ verify(repositoryManager).modify(any(Repository.class));
+ }
+
+
private PageResult createSingletonPageResult(Repository repository) {
return new PageResult<>(singletonList(repository), 0);
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
index bafb92fd62..cd86974c10 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
@@ -24,6 +24,7 @@
package sonia.scm.api.v2.resources;
+import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.RepositoryManager;
import static com.google.inject.util.Providers.of;
@@ -46,6 +47,7 @@ abstract class RepositoryTestBase {
IncomingRootResource incomingRootResource;
RepositoryCollectionResource repositoryCollectionResource;
AnnotateResource annotateResource;
+ ScmConfiguration scmConfiguration;
RepositoryRootResource getRepositoryRootResource() {
@@ -66,8 +68,8 @@ abstract class RepositoryTestBase {
repositoryToDtoMapper,
dtoToRepositoryMapper,
manager,
- repositoryBasedResourceProvider
- )),
+ repositoryBasedResourceProvider,
+ scmConfiguration)),
of(repositoryCollectionResource));
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java
index 53eb275d64..f1ffb2eda0 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java
@@ -33,6 +33,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.HealthCheckFailure;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command;
@@ -72,6 +73,8 @@ public class RepositoryToRepositoryDtoMapperTest {
private ScmPathInfoStore scmPathInfoStore;
@Mock
private ScmPathInfo uriInfo;
+ @Mock
+ private ScmConfiguration configuration;
@InjectMocks
private RepositoryToRepositoryDtoMapperImpl mapper;
@@ -83,6 +86,7 @@ public class RepositoryToRepositoryDtoMapperTest {
when(repositoryService.isSupported(any(Command.class))).thenReturn(true);
when(repositoryService.getSupportedProtocols()).thenReturn(of());
when(scmPathInfoStore.get()).thenReturn(uriInfo);
+ when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
}
@@ -129,6 +133,23 @@ public class RepositoryToRepositoryDtoMapperTest {
dto.getLinks().getLinkBy("update").get().getHref());
}
+ @Test
+ public void shouldCreateRenameLink() {
+ when(configuration.getNamespaceStrategy()).thenReturn("test");
+ RepositoryDto dto = mapper.map(createTestRepository());
+ assertEquals(
+ "http://example.com/base/v2/repositories/testspace/test/rename",
+ dto.getLinks().getLinkBy("rename").get().getHref());
+ }
+
+ @Test
+ public void shouldCreateRenameWithNamespaceLink() {
+ RepositoryDto dto = mapper.map(createTestRepository());
+ assertEquals(
+ "http://example.com/base/v2/repositories/testspace/test/rename",
+ dto.getLinks().getLinkBy("renameWithNamespace").get().getHref());
+ }
+
@Test
public void shouldMapHealthCheck() {
RepositoryDto dto = mapper.map(createTestRepository());
diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/rename-repo.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/rename-repo.json
new file mode 100644
index 0000000000..bb6fefb14a
--- /dev/null
+++ b/scm-webapp/src/test/resources/sonia/scm/api/v2/rename-repo.json
@@ -0,0 +1,4 @@
+{
+ "name": "x",
+ "namespace": "space"
+}
From e32130cd0b3e7e45d9b2ec17ccc1c859fc1fde85 Mon Sep 17 00:00:00 2001
From: Eduard Heimbuch
Date: Wed, 24 Jun 2020 10:39:00 +0200
Subject: [PATCH 02/20] create Dangerzone
---
scm-ui/ui-webapp/public/locales/de/repos.json | 21 ++-
scm-ui/ui-webapp/public/locales/en/repos.json | 21 ++-
.../src/repos/containers/DangerZone.tsx | 74 ++++++++++
.../src/repos/containers/DeleteRepo.tsx | 33 +++--
.../src/repos/containers/EditRepo.tsx | 10 +-
.../src/repos/containers/RenameRepository.tsx | 129 ++++++++++++++++++
6 files changed, 267 insertions(+), 21 deletions(-)
create mode 100644 scm-ui/ui-webapp/src/repos/containers/DangerZone.tsx
create mode 100644 scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json
index 882f63a7e6..e6906f8800 100644
--- a/scm-ui/ui-webapp/public/locales/de/repos.json
+++ b/scm-ui/ui-webapp/public/locales/de/repos.json
@@ -110,7 +110,8 @@
"repositoryForm": {
"subtitle": "Repository bearbeiten",
"submit": "Speichern",
- "initializeRepository": "Repository initiieren"
+ "initializeRepository": "Repository initiieren",
+ "dangerZone": "Gefahrenzone"
},
"sources": {
"file-tree": {
@@ -193,6 +194,8 @@
},
"deleteRepo": {
"button": "Repository löschen",
+ "subtitle": "Löscht dieses Repository",
+ "description": "Diese Aktion kann nicht rückgangig gemacht werden.",
"confirmAlert": {
"title": "Repository löschen",
"message": "Soll das Repository wirklich gelöscht werden?",
@@ -200,6 +203,22 @@
"cancel": "Nein"
}
},
+ "renameRepo": {
+ "button": "Repository umbenennen",
+ "subtitle": "Benennt dieses Repository um",
+ "description": "Es werden keine Weiterleitung auf den neuen Namen eingerichtet.",
+ "modal": {
+ "title": "Repository umbenennen",
+ "label": {
+ "repoName": "Repository Name",
+ "repoNamespace": "Repository Namespace"
+ },
+ "button": {
+ "rename": "Umbenennen",
+ "cancel": "Abbrechen"
+ }
+ }
+ },
"diff": {
"sideBySide": "Zur zweispaltigen Ansicht wechseln",
"combined": "Zur kombinierten Ansicht wechseln",
diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json
index 7102cff33a..8cafddf9f6 100644
--- a/scm-ui/ui-webapp/public/locales/en/repos.json
+++ b/scm-ui/ui-webapp/public/locales/en/repos.json
@@ -110,7 +110,8 @@
"repositoryForm": {
"subtitle": "Edit Repository",
"submit": "Save",
- "initializeRepository": "Initialize repository"
+ "initializeRepository": "Initialize repository",
+ "dangerZone": "Danger Zone"
},
"sources": {
"file-tree": {
@@ -193,6 +194,8 @@
},
"deleteRepo": {
"button": "Delete Repository",
+ "subtitle": "Deletes this repository",
+ "description": "Once a repository was deleted, this cannot be undone. Please be careful with this action.",
"confirmAlert": {
"title": "Delete repository",
"message": "Do you really want to delete the repository?",
@@ -200,6 +203,22 @@
"cancel": "No"
}
},
+ "renameRepo": {
+ "button": "Rename Repository",
+ "subtitle": "Renames this repository",
+ "description": "There will be no redirects to the renamed repository.",
+ "modal": {
+ "title": "Rename repository",
+ "label": {
+ "repoName": "Repository name",
+ "repoNamespace": "Repository namespace"
+ },
+ "button": {
+ "rename": "Rename",
+ "cancel": "Cancel"
+ }
+ }
+ },
"diff": {
"changes": {
"add": "added",
diff --git a/scm-ui/ui-webapp/src/repos/containers/DangerZone.tsx b/scm-ui/ui-webapp/src/repos/containers/DangerZone.tsx
new file mode 100644
index 0000000000..97cd60b68d
--- /dev/null
+++ b/scm-ui/ui-webapp/src/repos/containers/DangerZone.tsx
@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import React, { FC } from "react";
+import { Repository } from "@scm-manager/ui-types";
+import RenameRepository from "./RenameRepository";
+import DeleteRepo from "./DeleteRepo";
+import styled from "styled-components";
+import { Subtitle } from "@scm-manager/ui-components";
+import { useTranslation } from "react-i18next";
+
+type Props = {
+ repository: Repository;
+};
+
+const DangerZoneContainer = styled.div`
+ padding: 1rem;
+ border: 1px solid #ff6a88;
+ border-radius: 5px;
+ > *:not(:last-child) {
+ padding-bottom: 1.5rem;
+ border-bottom: solid 2px whitesmoke;
+ }
+`;
+
+const DangerZone: FC = ({ repository }) => {
+ const [t] = useTranslation("repos");
+
+ const dangerZone = [];
+ if (repository?._links?.rename) {
+ dangerZone.push( );
+ }
+ if (repository?._links?.renameWithNamespace) {
+ dangerZone.push( );
+ }
+ if (repository?._links?.delete) {
+ dangerZone.push( );
+ }
+
+ if (dangerZone.length === 0) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+ {dangerZone.map(entry => entry)}
+ >
+ );
+};
+
+export default DangerZone;
diff --git a/scm-ui/ui-webapp/src/repos/containers/DeleteRepo.tsx b/scm-ui/ui-webapp/src/repos/containers/DeleteRepo.tsx
index 4b782fbcaf..8585e98466 100644
--- a/scm-ui/ui-webapp/src/repos/containers/DeleteRepo.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/DeleteRepo.tsx
@@ -24,23 +24,21 @@
import React from "react";
import { connect } from "react-redux";
import { compose } from "redux";
-import { withRouter } from "react-router-dom";
+import { RouteComponentProps, withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history";
import { Repository } from "@scm-manager/ui-types";
-import { confirmAlert, DeleteButton, ErrorNotification, Level } from "@scm-manager/ui-components";
+import { confirmAlert, DeleteButton, ErrorNotification, Level, ButtonGroup } from "@scm-manager/ui-components";
import { deleteRepo, getDeleteRepoFailure, isDeleteRepoPending } from "../modules/repos";
-type Props = WithTranslation & {
- loading: boolean;
- error: Error;
- repository: Repository;
- confirmDialog?: boolean;
- deleteRepo: (p1: Repository, p2: () => void) => void;
-
- // context props
- history: History;
-};
+type Props = RouteComponentProps &
+ WithTranslation & {
+ loading: boolean;
+ error: Error;
+ repository: Repository;
+ confirmDialog?: boolean;
+ deleteRepo: (p1: Repository, p2: () => void) => void;
+ };
class DeleteRepo extends React.Component {
static defaultProps = {
@@ -88,9 +86,16 @@ class DeleteRepo extends React.Component {
return (
<>
-
- } />
+
+ {t("deleteRepo.subtitle")}
+ {t("deleteRepo.description")}
+
+ }
+ right={ }
+ />
>
);
}
diff --git a/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx b/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
index a6e9874628..a919423fa4 100644
--- a/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
@@ -25,13 +25,13 @@ import React from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import RepositoryForm from "../components/form";
-import DeleteRepo from "./DeleteRepo";
import { Repository } from "@scm-manager/ui-types";
import { getModifyRepoFailure, isModifyRepoPending, modifyRepo, modifyRepoReset } from "../modules/repos";
import { History } from "history";
import { ErrorNotification } from "@scm-manager/ui-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { compose } from "redux";
+import DangerZone from "./DangerZone";
type Props = {
loading: boolean;
@@ -79,7 +79,7 @@ class EditRepo extends React.Component {
};
return (
-
+ <>
{
}}
/>
-
-
+
+ >
);
}
}
@@ -116,4 +116,4 @@ const mapDispatchToProps = (dispatch: any) => {
};
};
-export default compose(connect(mapStateToProps, mapDispatchToProps), withRouter)(EditRepo);
+export default compose(connect(mapStateToProps, mapDispatchToProps))(withRouter(EditRepo));
diff --git a/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx b/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
new file mode 100644
index 0000000000..16ff616798
--- /dev/null
+++ b/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
@@ -0,0 +1,129 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import React, { FC, useState } from "react";
+import { Repository } from "@scm-manager/ui-types";
+import {
+ ErrorNotification,
+ Level,
+ Button,
+ Loading,
+ Modal,
+ InputField,
+ validation,
+ ButtonGroup
+} from "@scm-manager/ui-components";
+import { useTranslation } from "react-i18next";
+
+type Props = {
+ repository: Repository;
+ renameNamespace: boolean;
+};
+
+const RenameRepository: FC = ({ repository, renameNamespace }) => {
+ const [t] = useTranslation("repos");
+ const [error, setError] = useState(undefined);
+ const [loading, setLoading] = useState(false);
+ const [showModal, setShowModal] = useState(false);
+ const [repositoryName, setRepositoryName] = useState(repository.name);
+ const [repositoryNamespace, setRepositoryNamespace] = useState(repository.namespace);
+
+ if (error) {
+ return ;
+ }
+
+ if (loading) {
+ return ;
+ }
+
+ const isValid =
+ validation.isNameValid(repositoryName) &&
+ validation.isNameValid(repositoryNamespace) &&
+ (repository.name !== repositoryName || repository.namespace !== repositoryNamespace);
+
+ const modalBody = (
+
+
+ {renameNamespace && (
+
+ )}
+
+ );
+
+ const footer = (
+ <>
+
+
+ setShowModal(false)} />
+
+ >
+ );
+
+ return (
+ <>
+ setShowModal(false)}
+ />
+
+ {t("renameRepo.subtitle")}
+ {t("renameRepo.description")}
+
+ }
+ right={
+ setShowModal(true)}
+ loading={loading}
+ color="warning"
+ icon="edit"
+ />
+ }
+ />
+ >
+ );
+};
+
+export default RenameRepository;
From cd8a9873a957da545a5bca6f8045891e33038e93 Mon Sep 17 00:00:00 2001
From: Eduard Heimbuch
Date: Thu, 25 Jun 2020 09:16:19 +0200
Subject: [PATCH 03/20] - add global rename repositories permission - add api
call on rename action
---
.../repository/RepositoryRenamedEvent.java | 37 ++++++++++++++++
.../src/repos/containers/RenameRepository.tsx | 44 ++++++++++++++-----
.../src/repos/containers/RepositoryRoot.tsx | 7 +++
scm-ui/ui-webapp/src/repos/modules/repos.ts | 2 +-
.../api/v2/resources/RepositoryResource.java | 27 ++++++++----
.../resources/META-INF/scm/permissions.xml | 3 ++
.../main/resources/locales/de/plugins.json | 4 ++
.../main/resources/locales/en/plugins.json | 10 +++++
8 files changed, 113 insertions(+), 21 deletions(-)
create mode 100644 scm-core/src/main/java/sonia/scm/repository/RepositoryRenamedEvent.java
diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRenamedEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRenamedEvent.java
new file mode 100644
index 0000000000..4c68b6b332
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRenamedEvent.java
@@ -0,0 +1,37 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package sonia.scm.repository;
+
+import sonia.scm.HandlerEventType;
+import sonia.scm.event.AbstractHandlerEvent;
+import sonia.scm.event.Event;
+
+@Event
+public class RepositoryRenamedEvent extends AbstractHandlerEvent {
+
+ public RepositoryRenamedEvent(HandlerEventType eventType, Repository item, Repository oldItem) {
+ super(eventType, item, oldItem);
+ }
+}
diff --git a/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx b/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
index 16ff616798..5209d68f61 100644
--- a/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
@@ -23,7 +23,8 @@
*/
import React, { FC, useState } from "react";
-import { Repository } from "@scm-manager/ui-types";
+import { Repository, Link } from "@scm-manager/ui-types";
+import { CONTENT_TYPE } from "../modules/repos";
import {
ErrorNotification,
Level,
@@ -35,6 +36,8 @@ import {
ButtonGroup
} from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
+import { apiClient } from "@scm-manager/ui-components/src";
+import { useHistory } from "react-router-dom";
type Props = {
repository: Repository;
@@ -42,12 +45,13 @@ type Props = {
};
const RenameRepository: FC = ({ repository, renameNamespace }) => {
+ let history = useHistory();
const [t] = useTranslation("repos");
const [error, setError] = useState(undefined);
const [loading, setLoading] = useState(false);
const [showModal, setShowModal] = useState(false);
- const [repositoryName, setRepositoryName] = useState(repository.name);
- const [repositoryNamespace, setRepositoryNamespace] = useState(repository.namespace);
+ const [name, setName] = useState(repository.name);
+ const [namespace, setNamespace] = useState(repository.namespace);
if (error) {
return ;
@@ -58,24 +62,37 @@ const RenameRepository: FC = ({ repository, renameNamespace }) => {
}
const isValid =
- validation.isNameValid(repositoryName) &&
- validation.isNameValid(repositoryNamespace) &&
- (repository.name !== repositoryName || repository.namespace !== repositoryNamespace);
+ validation.isNameValid(name) &&
+ validation.isNameValid(namespace) &&
+ (repository.name !== name || repository.namespace !== namespace);
+
+ const rename = () => {
+ setLoading(true);
+ const url = renameNamespace
+ ? (repository?._links?.renameWithNamespace as Link).href
+ : (repository?._links?.rename as Link).href;
+
+ apiClient
+ .post(url, { name, namespace }, CONTENT_TYPE)
+ .then(() => setLoading(false))
+ .then(() => history.push(`/repo/${namespace}/${name}`))
+ .catch(setError);
+ };
const modalBody = (
{renameNamespace && (
)}
@@ -90,8 +107,13 @@ const RenameRepository: FC = ({ repository, renameNamespace }) => {
label={t("renameRepo.modal.button.rename")}
disabled={!isValid}
title={t("renameRepo.modal.button.rename")}
+ action={rename}
+ />
+ setShowModal(false)}
/>
- setShowModal(false)} />
>
);
diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx
index 38b0570cb5..f3d1aa6ac1 100644
--- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx
@@ -74,6 +74,13 @@ class RepositoryRoot extends React.Component {
fetchRepoByName(repoLink, namespace, name);
}
+ componentDidUpdate(prevProps: Props) {
+ const { fetchRepoByName, namespace, name, repoLink } = this.props;
+ if (namespace !== prevProps.namespace || name !== prevProps.name) {
+ fetchRepoByName(repoLink, namespace, name);
+ }
+ }
+
stripEndingSlash = (url: string) => {
if (url.endsWith("/")) {
return url.substring(0, url.length - 1);
diff --git a/scm-ui/ui-webapp/src/repos/modules/repos.ts b/scm-ui/ui-webapp/src/repos/modules/repos.ts
index e8360c5bbd..b9ea59b069 100644
--- a/scm-ui/ui-webapp/src/repos/modules/repos.ts
+++ b/scm-ui/ui-webapp/src/repos/modules/repos.ts
@@ -56,7 +56,7 @@ export const DELETE_REPO_PENDING = `${DELETE_REPO}_${types.PENDING_SUFFIX}`;
export const DELETE_REPO_SUCCESS = `${DELETE_REPO}_${types.SUCCESS_SUFFIX}`;
export const DELETE_REPO_FAILURE = `${DELETE_REPO}_${types.FAILURE_SUFFIX}`;
-const CONTENT_TYPE = "application/vnd.scmm-repository+json;v=2";
+export const CONTENT_TYPE = "application/vnd.scmm-repository+json;v=2";
// fetch repos
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
index a658973257..79b25c2a45 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
@@ -29,11 +29,14 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import sonia.scm.HandlerEventType;
import sonia.scm.config.ScmConfiguration;
+import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryPermissions;
+import sonia.scm.repository.RepositoryRenamedEvent;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
@@ -186,7 +189,7 @@ public class RepositoryResource {
*
* @param namespace the namespace of the repository to be modified
* @param name the name of the repository to be modified
- * @param renameDto renameDto object to modify
+ * @param renameDto renameDto object to modify
*/
@POST
@Path("rename")
@@ -205,26 +208,31 @@ public class RepositoryResource {
))
@ApiResponse(responseCode = "500", description = "internal server error")
public Response rename(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryRenameDto renameDto) {
- Repository repo = loadBy(namespace, name).get();
+ Supplier repoSupplier = loadBy(namespace, name);
+ Repository unchangedRepo = repoSupplier.get();
+ Repository changedRepo = unchangedRepo.clone();
- if (isRenameForbidden(repo)) {
+ if (isRenameForbidden(unchangedRepo, renameDto)) {
return Response.status(403).build();
}
- if (hasNamespaceOrNameNotChanged(repo, renameDto)) {
+ if (hasNamespaceOrNameNotChanged(unchangedRepo, renameDto)) {
return Response.status(400).build();
}
if (!Strings.isNullOrEmpty(renameDto.getName())) {
- repo.setName(renameDto.getName());
+ changedRepo.setName(renameDto.getName());
}
if (!Strings.isNullOrEmpty(renameDto.getNamespace())) {
- repo.setNamespace(renameDto.getNamespace());
+ changedRepo.setNamespace(renameDto.getNamespace());
}
return adapter.update(
- loadBy(namespace, name),
- existing -> repo,
+ repoSupplier,
+ existing -> {
+ ScmEventBus.getInstance().post(new RepositoryRenamedEvent(HandlerEventType.MODIFY, changedRepo, unchangedRepo));
+ return changedRepo;
+ },
changed -> true,
r -> r.getNamespaceAndName().logString()
);
@@ -235,8 +243,9 @@ public class RepositoryResource {
&& repo.getNamespace().equals(renameDto.getNamespace());
}
- private boolean isRenameForbidden(Repository repo) {
+ private boolean isRenameForbidden(Repository repo, RepositoryRenameDto renameDto) {
return !scmConfiguration.getNamespaceStrategy().equals("CustomNamespaceStrategy")
+ && !repo.getNamespace().equals(renameDto.getNamespace())
|| !RepositoryPermissions.rename(repo).isPermitted();
}
diff --git a/scm-webapp/src/main/resources/META-INF/scm/permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/permissions.xml
index 6e94e31214..bc50fbd3da 100644
--- a/scm-webapp/src/main/resources/META-INF/scm/permissions.xml
+++ b/scm-webapp/src/main/resources/META-INF/scm/permissions.xml
@@ -35,6 +35,9 @@
repository:read,pull,push:*
+
+ repository:read,rename:*
+
repository:*
diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json
index 4bf694129b..c6875c5f3b 100644
--- a/scm-webapp/src/main/resources/locales/de/plugins.json
+++ b/scm-webapp/src/main/resources/locales/de/plugins.json
@@ -103,6 +103,10 @@
"displayName": "Repository Löschen",
"description": "Darf das Repository löschen."
},
+ "rename": {
+ "displayName": "Repository umbenennen",
+ "description": "Darf das Repository umbenennen."
+ },
"pull": {
"displayName": "Pull/Checkout",
"description": "Darf pull/checkout auf das Repository ausführen."
diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json
index b75eb268c6..85e21aaaba 100644
--- a/scm-webapp/src/main/resources/locales/en/plugins.json
+++ b/scm-webapp/src/main/resources/locales/en/plugins.json
@@ -28,6 +28,12 @@
"description": "May see, clone and push to all repositories"
}
},
+ "read,rename": {
+ "*": {
+ "displayName": "Rename all repositories",
+ "description": "May see and rename all repositories"
+ }
+ },
"*": {
"displayName": "Own all repositories",
"description": "May see, clone, push to, configure and delete all repositories"
@@ -103,6 +109,10 @@
"displayName": "delete repository",
"description": "May delete the repository"
},
+ "rename": {
+ "displayName": "rename repository",
+ "description": "May rename the repository."
+ },
"pull": {
"displayName": "pull/checkout repository",
"description": "May pull/checkout the repository"
From 73aed0c610d3cd0ff6ebb3b92bff7c9f57b0910a Mon Sep 17 00:00:00 2001
From: Eduard Heimbuch
Date: Thu, 25 Jun 2020 12:28:26 +0200
Subject: [PATCH 04/20] inject scmEventBus with constructor injection
---
.../api/v2/resources/RepositoryResource.java | 6 ++--
.../resources/RepositoryRootResourceTest.java | 32 +++++++++++++++++--
.../api/v2/resources/RepositoryTestBase.java | 4 ++-
3 files changed, 36 insertions(+), 6 deletions(-)
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
index 79b25c2a45..f551e08988 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
@@ -66,19 +66,21 @@ public class RepositoryResource {
private final SingleResourceManagerAdapter adapter;
private final RepositoryBasedResourceProvider resourceProvider;
private final ScmConfiguration scmConfiguration;
+ private final ScmEventBus scmEventBus;
@Inject
public RepositoryResource(
RepositoryToRepositoryDtoMapper repositoryToDtoMapper,
RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, RepositoryManager manager,
RepositoryBasedResourceProvider resourceProvider,
- ScmConfiguration scmConfiguration) {
+ ScmConfiguration scmConfiguration, ScmEventBus scmEventBus) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper;
this.adapter = new SingleResourceManagerAdapter<>(manager, Repository.class);
this.resourceProvider = resourceProvider;
this.scmConfiguration = scmConfiguration;
+ this.scmEventBus = scmEventBus;
}
/**
@@ -230,7 +232,7 @@ public class RepositoryResource {
return adapter.update(
repoSupplier,
existing -> {
- ScmEventBus.getInstance().post(new RepositoryRenamedEvent(HandlerEventType.MODIFY, changedRepo, unchangedRepo));
+ scmEventBus.post(new RepositoryRenamedEvent(HandlerEventType.MODIFY, changedRepo, unchangedRepo));
return changedRepo;
},
changed -> true,
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
index 81f712b728..5c99939fc5 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
@@ -42,10 +42,12 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.PageResult;
import sonia.scm.config.ScmConfiguration;
+import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryInitializer;
import sonia.scm.repository.RepositoryManager;
+import sonia.scm.repository.RepositoryRenamedEvent;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.user.User;
@@ -108,6 +110,8 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
private RepositoryInitializer repositoryInitializer;
@Mock
private ScmConfiguration scmConfiguration;
+ @Mock
+ private ScmEventBus scmEventBus;
@Captor
private ArgumentCaptor> filterCaptor;
@@ -127,6 +131,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
super.manager = repositoryManager;
super.scmConfiguration = scmConfiguration;
+ super.scmEventBus = scmEventBus;
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
dispatcher.addSingletonResource(getRepositoryRootResource());
@@ -380,15 +385,15 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
}
@Test
- public void shouldNotRenameRepositoryIfNamespaceStrategyIsNotCustom() throws Exception {
- mockRepository("space", "repo");
+ public void shouldNotRenameRepositoryNamespaceIfNamespaceStrategyIsNotCustom() throws Exception {
+ mockRepository("hitchhiker", "heart-of-gold");
when(scmConfiguration.getNamespaceStrategy()).thenReturn("UsernameNamespaceStrategy");
URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
byte[] repository = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest
- .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
+ .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "hitchhiker/heart-of-gold/rename")
.contentType(VndMediaType.REPOSITORY)
.content(repository);
MockHttpResponse response = new MockHttpResponse();
@@ -433,6 +438,27 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
dispatcher.invoke(request, response);
+ assertEquals(SC_NO_CONTENT, response.getStatus());
+ verify(repositoryManager).modify(any(Repository.class));
+ verify(scmEventBus).post(any(RepositoryRenamedEvent.class));
+ }
+
+ @Test
+ public void shouldRenameRepositoryNameIfNamespaceStrategyNotCustom() throws Exception {
+ mockRepository("space", "repo");
+ when(scmConfiguration.getNamespaceStrategy()).thenReturn("UsernameNamespaceStrategy");
+
+ URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
+ byte[] repository = Resources.toByteArray(url);
+
+ MockHttpRequest request = MockHttpRequest
+ .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
+ .contentType(VndMediaType.REPOSITORY)
+ .content(repository);
+ MockHttpResponse response = new MockHttpResponse();
+
+ dispatcher.invoke(request, response);
+
assertEquals(SC_NO_CONTENT, response.getStatus());
verify(repositoryManager).modify(any(Repository.class));
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
index cd86974c10..ee5d4b2c42 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
@@ -25,6 +25,7 @@
package sonia.scm.api.v2.resources;
import sonia.scm.config.ScmConfiguration;
+import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.RepositoryManager;
import static com.google.inject.util.Providers.of;
@@ -48,6 +49,7 @@ abstract class RepositoryTestBase {
RepositoryCollectionResource repositoryCollectionResource;
AnnotateResource annotateResource;
ScmConfiguration scmConfiguration;
+ ScmEventBus scmEventBus;
RepositoryRootResource getRepositoryRootResource() {
@@ -69,7 +71,7 @@ abstract class RepositoryTestBase {
dtoToRepositoryMapper,
manager,
repositoryBasedResourceProvider,
- scmConfiguration)),
+ scmConfiguration, scmEventBus)),
of(repositoryCollectionResource));
}
}
From 1000cbc11334035e3f3ad4807e7d5587dbb48ae5 Mon Sep 17 00:00:00 2001
From: Eduard Heimbuch
Date: Thu, 25 Jun 2020 12:37:55 +0200
Subject: [PATCH 05/20] update CHANGELOG.md
---
CHANGELOG.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 828a313fa8..806bdb4d52 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
+## Added
+- Rename repository name (and namespace if permitted) ([#1218](https://github.com/scm-manager/scm-manager/pull/1218))
+
### Fixed
- Wait until recommended java installation is available for deb packages ([#1209](https://github.com/scm-manager/scm-manager/pull/1209))
- Do not force java home of recommended java dependency for rpm and deb packages ([#1195](https://github.com/scm-manager/scm-manager/issues/1195) and [#1208](https://github.com/scm-manager/scm-manager/pull/1208))
From 7fa256bedbccf8b1a98673855731db47fdbdfaf0 Mon Sep 17 00:00:00 2001
From: Eduard Heimbuch
Date: Fri, 26 Jun 2020 09:24:13 +0200
Subject: [PATCH 06/20] move rename logic to repository manager
---
...> ChangeNamespaceNotAllowedException.java} | 19 +-
.../scm/repository/RepositoryManager.java | 23 +-
.../RepositoryManagerDecorator.java | 47 +-
.../RepositoryModificationEvent.java | 2 +-
.../src/repos/containers/EditRepo.tsx | 2 +-
.../api/v2/resources/RepositoryResource.java | 56 +-
.../repository/DefaultRepositoryManager.java | 46 +-
.../main/resources/locales/de/plugins.json | 4 +
.../main/resources/locales/en/plugins.json | 4 +
.../resources/RepositoryRootResourceTest.java | 84 +-
.../api/v2/resources/RepositoryTestBase.java | 9 +-
.../DefaultRepositoryManagerTest.java | 963 ++++++++++--------
12 files changed, 619 insertions(+), 640 deletions(-)
rename scm-core/src/main/java/sonia/scm/repository/{RepositoryRenamedEvent.java => ChangeNamespaceNotAllowedException.java} (72%)
diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRenamedEvent.java b/scm-core/src/main/java/sonia/scm/repository/ChangeNamespaceNotAllowedException.java
similarity index 72%
rename from scm-core/src/main/java/sonia/scm/repository/RepositoryRenamedEvent.java
rename to scm-core/src/main/java/sonia/scm/repository/ChangeNamespaceNotAllowedException.java
index 4c68b6b332..783761e5ff 100644
--- a/scm-core/src/main/java/sonia/scm/repository/RepositoryRenamedEvent.java
+++ b/scm-core/src/main/java/sonia/scm/repository/ChangeNamespaceNotAllowedException.java
@@ -24,14 +24,19 @@
package sonia.scm.repository;
-import sonia.scm.HandlerEventType;
-import sonia.scm.event.AbstractHandlerEvent;
-import sonia.scm.event.Event;
+import sonia.scm.BadRequestException;
+import sonia.scm.ContextEntry;
-@Event
-public class RepositoryRenamedEvent extends AbstractHandlerEvent {
+public class ChangeNamespaceNotAllowedException extends BadRequestException {
- public RepositoryRenamedEvent(HandlerEventType eventType, Repository item, Repository oldItem) {
- super(eventType, item, oldItem);
+ public ChangeNamespaceNotAllowedException(Repository repository) {
+ super(ContextEntry.ContextBuilder.entity(repository).build(), "change of namespace is not allowed in current namespace strategy");
+ }
+
+ private static final String CODE = "ERS2vYb7U1";
+
+ @Override
+ public String getCode() {
+ return CODE;
}
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java
index b05a8fd01f..7269b3517f 100644
--- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java
+++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
@@ -38,18 +38,15 @@ import java.util.Collection;
* This class is a singleton and is available via injection.
*
* @author Sebastian Sdorra
- *
* @apiviz.uses sonia.scm.repository.RepositoryHandler
*/
public interface RepositoryManager
- extends TypeManager
-{
+ extends TypeManager {
/**
* Fire {@link RepositoryHookEvent} to the event bus.
*
* @param event hook event
- *
* @since 2.0.0
*/
public void fireHookEvent(RepositoryHookEvent event);
@@ -58,9 +55,7 @@ public interface RepositoryManager
* Imports an existing {@link Repository}.
* Note: This method should only be called from a {@link RepositoryHandler}.
*
- *
* @param repository {@link Repository} to import
- *
* @throws IOException
*/
public void importRepository(Repository repository) throws IOException;
@@ -71,10 +66,7 @@ public interface RepositoryManager
* Returns a {@link Repository} by its namespace and name or
* null if the {@link Repository} could not be found.
*
- *
* @param namespaceAndName namespace and name of the {@link Repository}
- *
- *
* @return {@link Repository} by its namespace and name or null
* if the {@link Repository} could not be found
*/
@@ -83,7 +75,6 @@ public interface RepositoryManager
/**
* Returns all configured repository types.
*
- *
* @return all configured repository types
*/
public Collection getConfiguredTypes();
@@ -91,11 +82,17 @@ public interface RepositoryManager
/**
* Returns a {@link RepositoryHandler} by the given type (hg, git, svn ...).
*
- *
* @param type the type of the {@link RepositoryHandler}
- *
* @return {@link RepositoryHandler} by the given type
*/
@Override
public RepositoryHandler getHandler(String type);
+
+ /**
+ * @param repository the repository {@link Repository}
+ * @param newNameSpace the new repository namespace
+ * @param newName the new repository name
+ * @return {@link Repository} the renamed repository
+ */
+ public Repository rename(Repository repository, String newNameSpace, String newName);
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java
index fa45106ab9..fdb8dfc1a8 100644
--- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java
+++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
@@ -42,17 +42,14 @@ import java.util.Collection;
*/
public class RepositoryManagerDecorator
extends ManagerDecorator
- implements RepositoryManager
-{
+ implements RepositoryManager {
/**
* Constructs ...
*
- *
* @param decorated
*/
- public RepositoryManagerDecorator(RepositoryManager decorated)
- {
+ public RepositoryManagerDecorator(RepositoryManager decorated) {
super(decorated);
this.decorated = decorated;
}
@@ -63,8 +60,7 @@ public class RepositoryManagerDecorator
* {@inheritDoc}
*/
@Override
- public void fireHookEvent(RepositoryHookEvent event)
- {
+ public void fireHookEvent(RepositoryHookEvent event) {
decorated.fireHookEvent(event);
}
@@ -79,65 +75,66 @@ public class RepositoryManagerDecorator
//~--- get methods ----------------------------------------------------------
@Override
- public Repository get(NamespaceAndName namespaceAndName)
- {
+ public Repository get(NamespaceAndName namespaceAndName) {
return decorated.get(namespaceAndName);
}
/**
* {@inheritDoc}
*
- *
* @return
*/
@Override
- public Collection getConfiguredTypes()
- {
+ public Collection getConfiguredTypes() {
return decorated.getConfiguredTypes();
}
/**
* Returns the decorated {@link RepositoryManager}.
*
- *
* @return decorated {@link RepositoryManager}
- *
* @since 1.34
*/
- public RepositoryManager getDecorated()
- {
+ public RepositoryManager getDecorated() {
return decorated;
}
/**
* {@inheritDoc}
*
- *
* @param type
- *
* @return
*/
@Override
@SuppressWarnings("unchecked")
- public RepositoryHandler getHandler(String type)
- {
+ public RepositoryHandler getHandler(String type) {
return decorated.getHandler(type);
}
/**
* {@inheritDoc}
*
+ * @return
+ */
+ @Override
+ public Collection getTypes() {
+ return decorated.getTypes();
+ }
+
+ /**
+ * {@inheritDoc}
*
* @return
*/
@Override
- public Collection getTypes()
- {
- return decorated.getTypes();
+ public Repository rename(Repository repository, String newNamespace, String newName) {
+ return decorated.rename(repository, newNamespace, newName);
}
//~--- fields ---------------------------------------------------------------
- /** Field description */
+ /**
+ * Field description
+ */
private final RepositoryManager decorated;
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryModificationEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryModificationEvent.java
index c61bbd2f03..8b199622d4 100644
--- a/scm-core/src/main/java/sonia/scm/repository/RepositoryModificationEvent.java
+++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryModificationEvent.java
@@ -49,7 +49,7 @@ public final class RepositoryModificationEvent extends RepositoryEvent implement
*/
public RepositoryModificationEvent(HandlerEventType eventType, Repository item, Repository itemBeforeModification)
{
- super(eventType, item);
+ super(eventType, item, itemBeforeModification);
this.itemBeforeModification = itemBeforeModification;
}
diff --git a/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx b/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
index a919423fa4..7b6a71b51c 100644
--- a/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
@@ -116,4 +116,4 @@ const mapDispatchToProps = (dispatch: any) => {
};
};
-export default compose(connect(mapStateToProps, mapDispatchToProps))(withRouter(EditRepo));
+export default compose(connect(mapStateToProps, mapDispatchToProps), withRouter)(EditRepo);
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
index f551e08988..12cc362517 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java
@@ -24,19 +24,13 @@
package sonia.scm.api.v2.resources;
-import com.google.common.base.Strings;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import sonia.scm.HandlerEventType;
-import sonia.scm.config.ScmConfiguration;
-import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
-import sonia.scm.repository.RepositoryPermissions;
-import sonia.scm.repository.RepositoryRenamedEvent;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
@@ -65,22 +59,17 @@ public class RepositoryResource {
private final RepositoryManager manager;
private final SingleResourceManagerAdapter adapter;
private final RepositoryBasedResourceProvider resourceProvider;
- private final ScmConfiguration scmConfiguration;
- private final ScmEventBus scmEventBus;
@Inject
public RepositoryResource(
RepositoryToRepositoryDtoMapper repositoryToDtoMapper,
RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, RepositoryManager manager,
- RepositoryBasedResourceProvider resourceProvider,
- ScmConfiguration scmConfiguration, ScmEventBus scmEventBus) {
+ RepositoryBasedResourceProvider resourceProvider) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper;
this.adapter = new SingleResourceManagerAdapter<>(manager, Repository.class);
this.resourceProvider = resourceProvider;
- this.scmConfiguration = scmConfiguration;
- this.scmEventBus = scmEventBus;
}
/**
@@ -210,48 +199,11 @@ public class RepositoryResource {
))
@ApiResponse(responseCode = "500", description = "internal server error")
public Response rename(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryRenameDto renameDto) {
- Supplier repoSupplier = loadBy(namespace, name);
- Repository unchangedRepo = repoSupplier.get();
- Repository changedRepo = unchangedRepo.clone();
-
- if (isRenameForbidden(unchangedRepo, renameDto)) {
- return Response.status(403).build();
- }
-
- if (hasNamespaceOrNameNotChanged(unchangedRepo, renameDto)) {
- return Response.status(400).build();
- }
-
- if (!Strings.isNullOrEmpty(renameDto.getName())) {
- changedRepo.setName(renameDto.getName());
- }
- if (!Strings.isNullOrEmpty(renameDto.getNamespace())) {
- changedRepo.setNamespace(renameDto.getNamespace());
- }
-
- return adapter.update(
- repoSupplier,
- existing -> {
- scmEventBus.post(new RepositoryRenamedEvent(HandlerEventType.MODIFY, changedRepo, unchangedRepo));
- return changedRepo;
- },
- changed -> true,
- r -> r.getNamespaceAndName().logString()
- );
+ Repository repository = loadBy(namespace, name).get();
+ manager.rename(repository, renameDto.getNamespace(), renameDto.getName());
+ return Response.status(204).build();
}
- private boolean hasNamespaceOrNameNotChanged(Repository repo, @Valid RepositoryRenameDto renameDto) {
- return repo.getName().equals(renameDto.getName())
- && repo.getNamespace().equals(renameDto.getNamespace());
- }
-
- private boolean isRenameForbidden(Repository repo, RepositoryRenameDto renameDto) {
- return !scmConfiguration.getNamespaceStrategy().equals("CustomNamespaceStrategy")
- && !repo.getNamespace().equals(renameDto.getNamespace())
- || !RepositoryPermissions.rename(repo).isPermitted();
- }
-
-
private Repository processUpdate(RepositoryDto repositoryDto, Repository existing) {
Repository changedRepository = dtoToRepositoryMapper.map(repositoryDto, existing.getId());
changedRepository.setPermissions(existing.getPermissions());
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java
index 285edc501f..db09d23e79 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java
@@ -21,10 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
import com.github.sdorra.ssp.PermissionActionCheck;
+import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
@@ -35,6 +36,7 @@ import org.slf4j.LoggerFactory;
import sonia.scm.ConfigurationException;
import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter;
+import sonia.scm.NoChangesMadeException;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider;
import sonia.scm.Type;
@@ -83,7 +85,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private final Provider namespaceStrategyProvider;
private final ManagerDaoAdapter managerDaoAdapter;
-
@Inject
public DefaultRepositoryManager(ScmConfiguration configuration,
SCMContextProvider contextProvider, KeyGenerator keyGenerator,
@@ -154,7 +155,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}
@Override
- public void delete(Repository repository){
+ public void delete(Repository repository) {
logger.info("delete repository {}/{} of type {}", repository.getNamespace(), repository.getName(), repository.getType());
managerDaoAdapter.delete(
repository,
@@ -179,7 +180,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
}
@Override
- public void modify(Repository repository){
+ public void modify(Repository repository) {
logger.info("modify repository {}/{} of type {}", repository.getNamespace(), repository.getName(), repository.getType());
managerDaoAdapter.modify(
@@ -243,6 +244,40 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return repository;
}
+ public Repository rename(Repository repository, String newNamespace, String newName) {
+
+ if (!configuration.getNamespaceStrategy().equals("CustomNamespaceStrategy")
+ && !repository.getNamespace().equals(newNamespace)) {
+ throw new ChangeNamespaceNotAllowedException(repository);
+ }
+
+ if (hasNamespaceOrNameNotChanged(repository, newNamespace, newName)) {
+ throw new NoChangesMadeException(repository);
+ }
+
+ Repository changedRepository = repository.clone();
+ if (!Strings.isNullOrEmpty(newName)) {
+ changedRepository.setName(newName);
+ }
+ if (!Strings.isNullOrEmpty(newNamespace)) {
+ changedRepository.setNamespace(newNamespace);
+ }
+
+ managerDaoAdapter.modify(
+ changedRepository,
+ RepositoryPermissions::rename,
+ notModified -> {
+ },
+ notModified -> fireEvent(HandlerEventType.MODIFY, changedRepository, repository));
+
+ return changedRepository;
+ }
+
+ private boolean hasNamespaceOrNameNotChanged(Repository repository, String newNamespace, String newName) {
+ return repository.getName().equals(newName)
+ && repository.getNamespace().equals(newNamespace);
+ }
+
@Override
public Collection getAll(Predicate filter, Comparator comparator) {
List repositories = Lists.newArrayList();
@@ -345,8 +380,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
types.add(type);
}
- private RepositoryHandler getHandler(Repository repository)
- {
+ private RepositoryHandler getHandler(Repository repository) {
String type = repository.getType();
RepositoryHandler handler = handlerMap.get(type);
diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json
index c6875c5f3b..eb9e390497 100644
--- a/scm-webapp/src/main/resources/locales/de/plugins.json
+++ b/scm-webapp/src/main/resources/locales/de/plugins.json
@@ -191,6 +191,10 @@
"displayName": "Es wurden keine Änderungen durchgeführt",
"description": "Das Repository wurde nicht verändert. Daher konnte kein neuer Commit erzeugt werden. Womöglich werden Änderungen aufgrund einer .ignore-Datei Definition nicht berücksichtigt."
},
+ "ERS2vYb7U1": {
+ "displayName": "Änderung des Namespace nicht möglich",
+ "description": "Namespaces dürfen nur mit der Namespace Strategie \"Benutzerdefiniert\" verändert werden."
+ },
"4iRct4avG1": {
"displayName": "Die Revisionen haben keinen gemeinsamen Ursprung",
"description": "Die Historie der Revisionen hat keinen gemeinsamen Urspung und kann somit auch nicht gegen einen solchen verglichen werden."
diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json
index 85e21aaaba..8a897f82ff 100644
--- a/scm-webapp/src/main/resources/locales/en/plugins.json
+++ b/scm-webapp/src/main/resources/locales/en/plugins.json
@@ -197,6 +197,10 @@
"displayName": "No changes were made",
"description": "No changes were made to the files of the repository. Therefor no new commit could be created. Possibly changes cannot be applied due to an .ignore-File definition."
},
+ "ERS2vYb7U1": {
+ "displayName": "Illegal change of namespace",
+ "description": "Namespaces can only be changed if namespace strategy is \"custom\"."
+ },
"4iRct4avG1": {
"displayName": "The revisions have unrelated histories",
"description": "The revisions have unrelated histories. Therefor there is no common commit to compare with."
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
index 5c99939fc5..a6b34b3fc4 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
@@ -27,7 +27,6 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.io.Resources;
-import com.google.inject.util.Providers;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.jboss.resteasy.mock.MockHttpRequest;
@@ -35,19 +34,16 @@ import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.jupiter.api.Nested;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.PageResult;
import sonia.scm.config.ScmConfiguration;
-import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryInitializer;
import sonia.scm.repository.RepositoryManager;
-import sonia.scm.repository.RepositoryRenamedEvent;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.user.User;
@@ -66,7 +62,6 @@ import static java.util.Collections.singletonList;
import static java.util.stream.Stream.of;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
import static javax.servlet.http.HttpServletResponse.SC_OK;
@@ -109,9 +104,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
@Mock
private RepositoryInitializer repositoryInitializer;
@Mock
- private ScmConfiguration scmConfiguration;
- @Mock
- private ScmEventBus scmEventBus;
+ private ScmConfiguration configuration;
@Captor
private ArgumentCaptor> filterCaptor;
@@ -130,14 +123,11 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
super.repositoryToDtoMapper = repositoryToDtoMapper;
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
super.manager = repositoryManager;
- super.scmConfiguration = scmConfiguration;
- super.scmEventBus = scmEventBus;
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
dispatcher.addSingletonResource(getRepositoryRootResource());
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(scmPathInfoStore.get()).thenReturn(uriInfo);
- when(scmConfiguration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
trillian.add(new User("trillian"), REALM);
@@ -164,6 +154,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
@Test
public void shouldFindExistingRepository() throws URISyntaxException, UnsupportedEncodingException {
mockRepository("space", "repo");
+ when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
MockHttpResponse response = new MockHttpResponse();
@@ -178,6 +169,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException {
PageResult singletonPageResult = createSingletonPageResult(mockRepository("space", "repo"));
when(repositoryManager.getPage(any(), any(), eq(0), eq(10))).thenReturn(singletonPageResult);
+ when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2);
MockHttpResponse response = new MockHttpResponse();
@@ -192,6 +184,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
public void shouldCreateFilterForSearch() throws URISyntaxException {
PageResult singletonPageResult = createSingletonPageResult(mockRepository("space", "repo"));
when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult);
+ when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?q=Rep");
MockHttpResponse response = new MockHttpResponse();
@@ -374,6 +367,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
public void shouldCreateArrayOfProtocolUrls() throws Exception {
mockRepository("space", "repo");
when(service.getSupportedProtocols()).thenReturn(of(new MockScmProtocol("http", "http://"), new MockScmProtocol("ssh", "ssh://")));
+ when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
MockHttpResponse response = new MockHttpResponse();
@@ -384,48 +378,12 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
assertTrue(response.getContentAsString().contains("\"protocol\":[{\"href\":\"http://\",\"name\":\"http\"},{\"href\":\"ssh://\",\"name\":\"ssh\"}]"));
}
- @Test
- public void shouldNotRenameRepositoryNamespaceIfNamespaceStrategyIsNotCustom() throws Exception {
- mockRepository("hitchhiker", "heart-of-gold");
- when(scmConfiguration.getNamespaceStrategy()).thenReturn("UsernameNamespaceStrategy");
-
- URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
- byte[] repository = Resources.toByteArray(url);
-
- MockHttpRequest request = MockHttpRequest
- .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "hitchhiker/heart-of-gold/rename")
- .contentType(VndMediaType.REPOSITORY)
- .content(repository);
- MockHttpResponse response = new MockHttpResponse();
-
- dispatcher.invoke(request, response);
-
- assertEquals(SC_FORBIDDEN, response.getStatus());
- }
-
- @Test
- public void shouldNotRenameRepositoryIfNamespaceAndNameDidNotChanged() throws Exception {
- mockRepository("space", "x");
- when(scmConfiguration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
-
- URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
- byte[] repository = Resources.toByteArray(url);
-
- MockHttpRequest request = MockHttpRequest
- .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/x/rename")
- .contentType(VndMediaType.REPOSITORY)
- .content(repository);
- MockHttpResponse response = new MockHttpResponse();
-
- dispatcher.invoke(request, response);
-
- assertEquals(SC_BAD_REQUEST, response.getStatus());
- }
-
@Test
public void shouldRenameRepository() throws Exception {
- mockRepository("space", "repo");
- when(scmConfiguration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
+ String namespace = "space";
+ String name = "repo";
+ Repository repository1 = mockRepository(namespace, name);
+ when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository1);
URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
byte[] repository = Resources.toByteArray(url);
@@ -439,31 +397,9 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
dispatcher.invoke(request, response);
assertEquals(SC_NO_CONTENT, response.getStatus());
- verify(repositoryManager).modify(any(Repository.class));
- verify(scmEventBus).post(any(RepositoryRenamedEvent.class));
+ verify(repositoryManager).rename(repository1, "space", "x");
}
- @Test
- public void shouldRenameRepositoryNameIfNamespaceStrategyNotCustom() throws Exception {
- mockRepository("space", "repo");
- when(scmConfiguration.getNamespaceStrategy()).thenReturn("UsernameNamespaceStrategy");
-
- URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
- byte[] repository = Resources.toByteArray(url);
-
- MockHttpRequest request = MockHttpRequest
- .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
- .contentType(VndMediaType.REPOSITORY)
- .content(repository);
- MockHttpResponse response = new MockHttpResponse();
-
- dispatcher.invoke(request, response);
-
- assertEquals(SC_NO_CONTENT, response.getStatus());
- verify(repositoryManager).modify(any(Repository.class));
- }
-
-
private PageResult createSingletonPageResult(Repository repository) {
return new PageResult<>(singletonList(repository), 0);
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
index ee5d4b2c42..58ec1ffb86 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java
@@ -24,12 +24,9 @@
package sonia.scm.api.v2.resources;
-import sonia.scm.config.ScmConfiguration;
-import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.RepositoryManager;
import static com.google.inject.util.Providers.of;
-import static org.mockito.Mockito.mock;
abstract class RepositoryTestBase {
@@ -48,9 +45,6 @@ abstract class RepositoryTestBase {
IncomingRootResource incomingRootResource;
RepositoryCollectionResource repositoryCollectionResource;
AnnotateResource annotateResource;
- ScmConfiguration scmConfiguration;
- ScmEventBus scmEventBus;
-
RepositoryRootResource getRepositoryRootResource() {
RepositoryBasedResourceProvider repositoryBasedResourceProvider = new RepositoryBasedResourceProvider(
@@ -70,8 +64,7 @@ abstract class RepositoryTestBase {
repositoryToDtoMapper,
dtoToRepositoryMapper,
manager,
- repositoryBasedResourceProvider,
- scmConfiguration, scmEventBus)),
+ repositoryBasedResourceProvider)),
of(repositoryCollectionResource));
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
index f8c084f167..326b1f59e7 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
@@ -30,7 +30,6 @@ import com.github.legman.Subscribe;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
import com.google.inject.util.Providers;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.util.ThreadContext;
@@ -44,32 +43,46 @@ import sonia.scm.AlreadyExistsException;
import sonia.scm.HandlerEventType;
import sonia.scm.Manager;
import sonia.scm.ManagerTestBase;
+import sonia.scm.NoChangesMadeException;
import sonia.scm.NotFoundException;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.event.ScmEventBus;
-import sonia.scm.io.DefaultFileSystem;
import sonia.scm.repository.api.HookContext;
import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.HookFeature;
import sonia.scm.repository.spi.HookContextProvider;
-import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
-import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.KeyGenerator;
import sonia.scm.store.ConfigurationStoreFactory;
-import sonia.scm.store.JAXBConfigurationStoreFactory;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
import java.util.Stack;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static java.util.Collections.emptySet;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasProperty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------
@@ -83,447 +96,491 @@ import static org.mockito.Mockito.*;
password = "secret",
configuration = "classpath:sonia/scm/repository/shiro.ini"
)
-public class DefaultRepositoryManagerTest {//extends ManagerTestBase {
+public class DefaultRepositoryManagerTest extends ManagerTestBase {
+
+ {
+ ThreadContext.unbindSubject();
+ }
+
+ @Rule
+ public ShiroRule shiro = new ShiroRule();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private ScmConfiguration configuration;
+
+ private String mockedNamespace = "default_namespace";
+
+ @Before
+ public void initContext() {
+ ((TempSCMContextProvider) SCMContext.getContext()).setBaseDirectory(temp);
+ }
+
+ @Test
+ public void testCreate() {
+ Repository heartOfGold = createTestRepository();
+ Repository dbRepo = manager.get(heartOfGold.getId());
+
+ assertNotNull(dbRepo);
+ assertRepositoriesEquals(dbRepo, heartOfGold);
+ }
+
+ @SubjectAware(
+ username = "unpriv"
+ )
+ @Test(expected = UnauthorizedException.class)
+ public void testCreateWithoutPrivileges() {
+ createTestRepository();
+ }
+
+ @Test
+ public void testCreateExisting() {
+ createTestRepository();
+ thrown.expect(AlreadyExistsException.class);
+ createTestRepository();
+ }
+
+ @Test
+ public void testDelete() {
+ delete(manager, createTestRepository());
+ }
+
+ @SubjectAware(
+ username = "unpriv"
+ )
+ @Test(expected = UnauthorizedException.class)
+ public void testDeleteWithoutPrivileges() {
+ delete(manager, createTestRepository());
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testDeleteNotFound() {
+ manager.delete(createRepositoryWithId());
+ }
+
+ @Test
+ public void testGet() {
+ Repository heartOfGold = createTestRepository();
+ String id = heartOfGold.getId();
+ String description = heartOfGold.getDescription();
+
+ assertNotNull(description);
+
+ // test for reference
+ heartOfGold.setDescription("prototype ship");
+ heartOfGold = manager.get(id);
+ assertNotNull(heartOfGold);
+ assertEquals(description, heartOfGold.getDescription());
+ }
+
+ @Test
+ @SubjectAware(
+ username = "crato"
+ )
+ public void testGetWithoutRequiredPrivileges() {
+ Repository heartOfGold = RepositoryTestData.createHeartOfGold();
+ manager.create(heartOfGold);
+
+ thrown.expect(UnauthorizedException.class);
+ manager.get(heartOfGold.getId());
+ }
+
+ @Test
+ public void testGetAll() {
+ Repository heartOfGold = createTestRepository();
+ Repository happyVerticalPeopleTransporter = createSecondTestRepository();
+ boolean foundHeart = false;
+ boolean foundTransporter = false;
+ Collection repositories = manager.getAll();
+
+ assertNotNull(repositories);
+ assertFalse(repositories.isEmpty());
+ assertTrue(repositories.size() >= 2);
+
+ Repository heartReference = null;
+
+ for (Repository repository : repositories) {
+ if (repository.getId().equals(heartOfGold.getId())) {
+ assertRepositoriesEquals(heartOfGold, repository);
+ foundHeart = true;
+ heartReference = repository;
+ } else if (repository.getId().equals(happyVerticalPeopleTransporter.getId())) {
+ assertRepositoriesEquals(happyVerticalPeopleTransporter, repository);
+ foundTransporter = true;
+ }
+ }
+
+ assertTrue(foundHeart);
+ assertTrue(foundTransporter);
+
+ // test for reference
+ assertNotSame(heartOfGold, heartReference);
+ heartReference.setDescription("prototype ship");
+ assertFalse(
+ heartOfGold.getDescription().equals(heartReference.getDescription()));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @SubjectAware(username = "dent")
+ public void testGetAllWithPermissionsForTwoOrThreeRepos() {
+ // mock key generator
+ KeyGenerator keyGenerator = mock(KeyGenerator.class);
+ Stack keys = new Stack<>();
+ keys.push("rateotu");
+ keys.push("p42");
+ keys.push("hof");
+
+ when(keyGenerator.createKey()).then((InvocationOnMock invocation) -> {
+ return keys.pop();
+ });
+
+ // create repository manager
+ RepositoryManager repositoryManager = createRepositoryManager(keyGenerator);
+
+ // create first test repository
+ Repository heartOfGold = RepositoryTestData.createHeartOfGold();
+ repositoryManager.create(heartOfGold);
+ assertEquals("hof", heartOfGold.getId());
+
+ // create second test repository
+ Repository puzzle42 = RepositoryTestData.create42Puzzle();
+ repositoryManager.create(puzzle42);
+ assertEquals("p42", puzzle42.getId());
+
+ // create third test repository
+ Repository restaurant = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
+ repositoryManager.create(restaurant);
+ assertEquals("rateotu", restaurant.getId());
+
+ // assert returned repositories
+ Collection repositories = repositoryManager.getAll();
+ assertEquals(2, repositories.size());
+ assertThat(repositories, containsInAnyOrder(
+ hasProperty("id", is("p42")),
+ hasProperty("id", is("hof"))
+ )
+ );
+ }
+
+ @Test
+ public void testEvents() {
+ RepositoryManager repoManager = createManager();
+ repoManager.init(contextProvider);
+ TestListener listener = new TestListener();
+
+ ScmEventBus.getInstance().register(listener);
+
+ Repository repository = RepositoryTestData.create42Puzzle();
+
+ repoManager.create(repository);
+ assertRepositoriesEquals(repository, listener.preRepository);
+ assertSame(HandlerEventType.BEFORE_CREATE, listener.preEvent);
+ assertRepositoriesEquals(repository, listener.postRepository);
+ assertSame(HandlerEventType.CREATE, listener.postEvent);
+
+ repository.setDescription("changed description");
+ repoManager.modify(repository);
+ assertRepositoriesEquals(repository, listener.preRepository);
+ assertSame(HandlerEventType.BEFORE_MODIFY, listener.preEvent);
+ assertRepositoriesEquals(repository, listener.postRepository);
+ assertSame(HandlerEventType.MODIFY, listener.postEvent);
+
+ repoManager.delete(repository);
+
+ assertRepositoriesEquals(repository, listener.preRepository);
+ assertSame(HandlerEventType.BEFORE_DELETE, listener.preEvent);
+ assertRepositoriesEquals(repository, listener.postRepository);
+ assertSame(HandlerEventType.DELETE, listener.postEvent);
+ }
+
+ @Test
+ public void testModify() {
+ Repository heartOfGold = createTestRepository();
+
+ heartOfGold.setDescription("prototype ship");
+ manager.modify(heartOfGold);
+
+ Repository hearReference = manager.get(heartOfGold.getId());
+
+ assertNotNull(hearReference);
+ assertEquals(hearReference.getDescription(), "prototype ship");
+ }
+
+ @Test
+ @SubjectAware(username = "crato")
+ public void testModifyWithoutRequiredPermissions() {
+ Repository heartOfGold = RepositoryTestData.createHeartOfGold();
+ manager.create(heartOfGold);
+ heartOfGold.setDescription("prototype ship");
+
+ thrown.expect(UnauthorizedException.class);
+ manager.modify(heartOfGold);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testModifyNotFound() {
+ manager.modify(createRepositoryWithId());
+ }
+
+ @Test
+ public void testRefresh() {
+ Repository heartOfGold = createTestRepository();
+ String description = heartOfGold.getDescription();
+
+ heartOfGold.setDescription("prototype ship");
+ manager.refresh(heartOfGold);
+ assertEquals(description, heartOfGold.getDescription());
+ }
+
+ @Test
+ @SubjectAware(username = "crato")
+ public void testRefreshWithoutRequiredPermissions() {
+ Repository heartOfGold = RepositoryTestData.createHeartOfGold();
+ manager.create(heartOfGold);
+ heartOfGold.setDescription("prototype ship");
+
+ thrown.expect(UnauthorizedException.class);
+ manager.refresh(heartOfGold);
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void testRefreshNotFound() {
+ manager.refresh(createRepositoryWithId());
+ }
+
+ @Test
+ public void testRepositoryHook() {
+ CountingReceiveHook hook = new CountingReceiveHook();
+ RepositoryManager repoManager = createManager();
+
+ ScmEventBus.getInstance().register(hook);
+
+ assertEquals(0, hook.eventsReceived);
+
+ Repository repository = createTestRepository();
+ HookContext ctx = createHookContext(repository);
+
+ repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
+ RepositoryHookType.POST_RECEIVE));
+ assertEquals(1, hook.eventsReceived);
+ repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
+ RepositoryHookType.POST_RECEIVE));
+ assertEquals(2, hook.eventsReceived);
+ }
+
+ @Test
+ public void testNamespaceSet() {
+ RepositoryManager repoManager = createManager();
+ Repository repository = spy(createTestRepository());
+ repository.setName("Testrepo");
+ repoManager.create(repository);
+ assertEquals("default_namespace", repository.getNamespace());
+ }
+
+ @Test
+ public void shouldSetNamespace() {
+ Repository repository = new Repository(null, "hg", null, "scm");
+ manager.create(repository);
+ assertNotNull(repository.getId());
+ assertNotNull(repository.getNamespace());
+ }
+
+ @Test
+ public void shouldThrowChangeNamespaceNotAllowedException() {
+ Repository repository = new Repository("1", "hg", "space", "x");
+ RepositoryManager repoManager = createManager();
+ thrown.expect(ChangeNamespaceNotAllowedException.class);
+
+ repoManager.rename(repository, "hitchhiker", "heart-of-gold");
+ }
+
+ @Test
+ public void shouldThrowNoChangesMadeException() {
+ configuration.setNamespaceStrategy("CustomNamespaceStrategy");
+ Repository repository = new Repository("1", "hg", "space", "x");
+ RepositoryManager repoManager = createManager();
+
+ thrown.expect(NoChangesMadeException.class);
+
+ repoManager.rename(repository, "space", "x");
+ }
+
+ @Test
+ public void shouldOnlyChangeRepositoryName() {
+ Repository repository = createTestRepository();
+ RepositoryManager repoManager = (RepositoryManager) manager;
+ configuration.setNamespaceStrategy("UsernameNamespaceStrategy");
+
+ Repository changedRepo = repoManager.rename(repository, "default_namespace", "puzzle42");
+ assertNotEquals(changedRepo.getName(), repository.getName());
+ }
+
+ @Test
+ public void shouldRenameRepositoryNamespaceAndName() {
+ Repository repository = createTestRepository();
+ RepositoryManager repoManager = (RepositoryManager) manager;
+ configuration.setNamespaceStrategy("CustomNamespaceStrategy");
+
+ Repository changedRepo = repoManager.rename(repository, "hitchhiker", "puzzle42");
+ assertNotEquals(changedRepo.getName(), repository.getName());
+ assertNotEquals(changedRepo.getNamespace(), repository.getNamespace());
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ @Override
+ protected DefaultRepositoryManager createManager() {
+ return createRepositoryManager(new DefaultKeyGenerator());
+ }
+
+ private DefaultRepositoryManager createRepositoryManager(KeyGenerator keyGenerator) {
+ Set handlerSet = new HashSet<>();
+ RepositoryDAO repositoryDAO = createRepositoryDaoMock();
+ mock(ConfigurationStoreFactory.class);
+ handlerSet.add(createRepositoryHandler("dummy", "Dummy"));
+ handlerSet.add(createRepositoryHandler("git", "Git"));
+ handlerSet.add(createRepositoryHandler("hg", "Mercurial"));
+ handlerSet.add(createRepositoryHandler("svn", "SVN"));
+
+ this.configuration = new ScmConfiguration();
+
+ NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
+ when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
+
+ return new DefaultRepositoryManager(configuration, contextProvider,
+ keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy));
+ }
+
+ private RepositoryDAO createRepositoryDaoMock() {
+ Map repositoriesById = new HashMap<>();
+ Map repositoriesByNamespaceAndName = new HashMap<>();
+ RepositoryDAO mock = mock(RepositoryDAO.class);
+ doAnswer(invocation -> {
+ Repository repo = invocation.getArgument(0, Repository.class);
+ if (repositoriesById.containsKey(repo.getId()) || repositoriesByNamespaceAndName.containsKey(repo.getNamespaceAndName())) {
+ throw new AlreadyExistsException(repo);
+ }
+ Repository clone = repo.clone();
+ repositoriesById.put(repo.getId(), clone);
+ repositoriesByNamespaceAndName.put(repo.getNamespaceAndName(), clone);
+ return null;
+ }).when(mock).add(any());
+ doAnswer(invocation -> {
+ Repository repo = invocation.getArgument(0, Repository.class);
+ Repository clone = repo.clone();
+ repositoriesById.put(repo.getId(), clone);
+ repositoriesByNamespaceAndName.put(repo.getNamespaceAndName(), clone);
+ return null;
+ }).when(mock).modify(any());
+ when(mock.get(anyString())).thenAnswer(invocation -> repositoriesById.get(invocation.getArgument(0, String.class)));
+ when(mock.get(any(NamespaceAndName.class))).thenAnswer(invocation -> repositoriesByNamespaceAndName.get(invocation.getArgument(0, NamespaceAndName.class)));
+ when(mock.getAll()).thenAnswer(invocation -> repositoriesById.values());
+ when(mock.contains(anyString())).thenAnswer(invocation -> repositoriesById.containsKey(invocation.getArgument(0, String.class)));
+ when(mock.contains(any(Repository.class))).thenAnswer(invocation -> repositoriesById.containsKey(invocation.getArgument(0, Repository.class).getId()));
+ doAnswer(invocation -> {
+ Repository repo = invocation.getArgument(0, Repository.class);
+ repositoriesById.remove(repo.getId());
+ repositoriesByNamespaceAndName.remove(repo.getNamespaceAndName());
+ return null;
+ }).when(mock).delete(any(Repository.class));
+ return mock;
+ }
+
+ private RepositoryHandler createRepositoryHandler(String name, String diplayName) {
+ RepositoryHandler handler = mock(RepositoryHandler.class);
+ when(handler.getType()).thenReturn(new RepositoryType(name, diplayName, emptySet()));
+ when(handler.isConfigured()).thenReturn(true);
+ return handler;
+ }
+
+ private HookContext createHookContext(Repository repository) {
+ PreProcessorUtil ppu = mock(PreProcessorUtil.class);
+ HookContextProvider provider = mock(HookContextProvider.class);
+ Set features = ImmutableSet.of();
+
+ when(provider.getSupportedFeatures()).thenReturn(features);
+
+ return new HookContextFactory(ppu).createContext(provider, repository);
+ }
+
+ private void assertRepositoriesEquals(Repository repo, Repository other) {
+ assertEquals(repo.getId(), other.getId());
+ assertEquals(repo.getName(), other.getName());
+ assertEquals(repo.getDescription(), other.getDescription());
+ assertEquals(repo.getContact(), other.getContact());
+ assertEquals(repo.getCreationDate(), other.getCreationDate());
+ assertEquals(repo.getLastModified(), other.getLastModified());
+ }
+
+ private Repository createRepository(Repository repository) {
+ manager.create(repository);
+ assertNotNull(repository.getId());
+ assertNotNull(manager.get(repository.getId()));
+ assertTrue(repository.getCreationDate() > 0);
+
+ return repository;
+ }
+
+ private Repository createRepositoryWithId() {
+ Repository repository = RepositoryTestData.createHeartOfGold();
+ repository.setId("abc");
+ return repository;
+ }
+
+ private Repository createSecondTestRepository() {
+ return createRepository(
+ RepositoryTestData.createHappyVerticalPeopleTransporter());
+ }
+
+ private Repository createTestRepository() {
+ return createRepository(RepositoryTestData.createHeartOfGold());
+ }
+
+ private void delete(Manager manager, Repository repository) {
+
+ String id = repository.getId();
+
+ manager.delete(repository);
+ assertNull(manager.get(id));
+ }
+
+ private static class CountingReceiveHook {
+
+ private int eventsReceived = 0;
+
+ @Subscribe(async = false)
+ public void onEvent(PostReceiveRepositoryHookEvent event) {
+ eventsReceived++;
+ }
+
+ @Subscribe(async = false)
+ public void onEvent(PreReceiveRepositoryHookEvent event) {
+ eventsReceived++;
+ }
+ }
+
+ private class TestListener {
+
+ private HandlerEventType postEvent;
+
+ private Repository postRepository;
+
+ private HandlerEventType preEvent;
+
+ private Repository preRepository;
+
+ @Subscribe(async = false)
+ public void onEvent(RepositoryEvent event) {
+ if (event.getEventType().isPost()) {
+ this.postRepository = event.getItem();
+ this.postEvent = event.getEventType();
+ } else if (event.getEventType().isPre()) {
+ this.preRepository = event.getItem();
+ this.preEvent = event.getEventType();
+ }
+ }
+ }
-// {
-// ThreadContext.unbindSubject();
-// }
-//
-// @Rule
-// public ShiroRule shiro = new ShiroRule();
-//
-// @Rule
-// public ExpectedException thrown = ExpectedException.none();
-//
-// private ScmConfiguration configuration;
-//
-// private String mockedNamespace = "default_namespace";
-//
-// @Before
-// public void initContext() {
-// ((TempSCMContextProvider)SCMContext.getContext()).setBaseDirectory(temp);
-// }
-//
-// @Test
-// public void testCreate() {
-// Repository heartOfGold = createTestRepository();
-// Repository dbRepo = manager.get(heartOfGold.getId());
-//
-// assertNotNull(dbRepo);
-// assertRepositoriesEquals(dbRepo, heartOfGold);
-// }
-//
-// @SubjectAware(
-// username = "unpriv"
-// )
-// @Test(expected = UnauthorizedException.class)
-// public void testCreateWithoutPrivileges() {
-// createTestRepository();
-// }
-//
-// @Test
-// public void testCreateExisting() {
-// Repository testRepository = createTestRepository();
-// String expectedNamespaceAndName = testRepository.getNamespaceAndName().logString();
-// thrown.expect(AlreadyExistsException.class);
-// thrown.expectMessage(expectedNamespaceAndName);
-// createTestRepository();
-// }
-//
-// @Test
-// public void testDelete() {
-// delete(manager, createTestRepository());
-// }
-//
-// @SubjectAware(
-// username = "unpriv"
-// )
-// @Test(expected = UnauthorizedException.class)
-// public void testDeleteWithoutPrivileges() {
-// delete(manager, createTestRepository());
-// }
-//
-// @Test(expected = RepositoryIsNotArchivedException.class)
-// public void testDeleteNonArchived() {
-// configuration.setEnableRepositoryArchive(true);
-// delete(manager, createTestRepository());
-// }
-//
-// @Test(expected = NotFoundException.class)
-// public void testDeleteNotFound(){
-// manager.delete(createRepositoryWithId());
-// }
-//
-// @Test
-// public void testDeleteWithEnabledArchive() {
-// Repository repository = createTestRepository();
-//
-// repository.setArchived(true);
-// RepositoryManager drm = createRepositoryManager(true);
-// drm.init(contextProvider);
-// delete(drm, repository);
-// }
-//
-// @Test
-// public void testGet() {
-// Repository heartOfGold = createTestRepository();
-// String id = heartOfGold.getId();
-// String description = heartOfGold.getDescription();
-//
-// assertNotNull(description);
-//
-// // test for reference
-// heartOfGold.setDescription("prototype ship");
-// heartOfGold = manager.get(id);
-// assertNotNull(heartOfGold);
-// assertEquals(description, heartOfGold.getDescription());
-// }
-//
-// @Test
-// @SubjectAware(
-// username = "crato"
-// )
-// public void testGetWithoutRequiredPrivileges() {
-// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
-// manager.create(heartOfGold);
-//
-// thrown.expect(UnauthorizedException.class);
-// manager.get(heartOfGold.getId());
-// }
-//
-// @Test
-// public void testGetAll() {
-// Repository heartOfGold = createTestRepository();
-// Repository happyVerticalPeopleTransporter = createSecondTestRepository();
-// boolean foundHeart = false;
-// boolean foundTransporter = false;
-// Collection repositories = manager.getAll();
-//
-// assertNotNull(repositories);
-// assertFalse(repositories.isEmpty());
-// assertTrue(repositories.size() >= 2);
-//
-// Repository heartReference = null;
-//
-// for (Repository repository : repositories) {
-// if (repository.getId().equals(heartOfGold.getId())) {
-// assertRepositoriesEquals(heartOfGold, repository);
-// foundHeart = true;
-// heartReference = repository;
-// }
-// else if (repository.getId().equals(happyVerticalPeopleTransporter.getId())) {
-// assertRepositoriesEquals(happyVerticalPeopleTransporter, repository);
-// foundTransporter = true;
-// }
-// }
-//
-// assertTrue(foundHeart);
-// assertTrue(foundTransporter);
-//
-// // test for reference
-// assertNotSame(heartOfGold, heartReference);
-// heartReference.setDescription("prototype ship");
-// assertFalse(
-// heartOfGold.getDescription().equals(heartReference.getDescription()));
-// }
-//
-// @Test
-// @SuppressWarnings("unchecked")
-// @SubjectAware(username = "dent")
-// public void testGetAllWithPermissionsForTwoOrThreeRepos() {
-// // mock key generator
-// KeyGenerator keyGenerator = mock(KeyGenerator.class);
-// Stack keys = new Stack<>();
-// keys.push("rateotu");
-// keys.push("p42");
-// keys.push("hof");
-//
-// when(keyGenerator.createKey()).then((InvocationOnMock invocation) -> {
-// return keys.pop();
-// });
-//
-// // create repository manager
-// RepositoryManager repositoryManager = createRepositoryManager(false, keyGenerator);
-//
-// // create first test repository
-// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
-// repositoryManager.create(heartOfGold);
-// assertEquals("hof", heartOfGold.getId());
-//
-// // create second test repository
-// Repository puzzle42 = RepositoryTestData.create42Puzzle();
-// repositoryManager.create(puzzle42);
-// assertEquals("p42", puzzle42.getId());
-//
-// // create third test repository
-// Repository restaurant = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
-// repositoryManager.create(restaurant);
-// assertEquals("rateotu", restaurant.getId());
-//
-// // assert returned repositories
-// Collection repositories = repositoryManager.getAll();
-// assertEquals(2, repositories.size());
-// assertThat(repositories, containsInAnyOrder(
-// hasProperty("id", is("p42")),
-// hasProperty("id", is("hof"))
-// )
-// );
-// }
-//
-// @Test
-// public void testEvents() {
-// RepositoryManager repoManager = createRepositoryManager(false);
-// repoManager.init(contextProvider);
-// TestListener listener = new TestListener();
-//
-// ScmEventBus.getInstance().register(listener);
-//
-// Repository repository = RepositoryTestData.create42Puzzle();
-//
-// repoManager.create(repository);
-// assertRepositoriesEquals(repository, listener.preRepository);
-// assertSame(HandlerEventType.BEFORE_CREATE, listener.preEvent);
-// assertRepositoriesEquals(repository, listener.postRepository);
-// assertSame(HandlerEventType.CREATE, listener.postEvent);
-//
-// repository.setDescription("changed description");
-// repoManager.modify(repository);
-// assertRepositoriesEquals(repository, listener.preRepository);
-// assertSame(HandlerEventType.BEFORE_MODIFY, listener.preEvent);
-// assertRepositoriesEquals(repository, listener.postRepository);
-// assertSame(HandlerEventType.MODIFY, listener.postEvent);
-//
-// repoManager.delete(repository);
-//
-// assertRepositoriesEquals(repository, listener.preRepository);
-// assertSame(HandlerEventType.BEFORE_DELETE, listener.preEvent);
-// assertRepositoriesEquals(repository, listener.postRepository);
-// assertSame(HandlerEventType.DELETE, listener.postEvent);
-// }
-//
-// @Test
-// public void testModify() {
-// Repository heartOfGold = createTestRepository();
-//
-// heartOfGold.setDescription("prototype ship");
-// manager.modify(heartOfGold);
-//
-// Repository hearReference = manager.get(heartOfGold.getId());
-//
-// assertNotNull(hearReference);
-// assertEquals(hearReference.getDescription(), "prototype ship");
-// }
-//
-// @Test
-// @SubjectAware(username = "crato")
-// public void testModifyWithoutRequiredPermissions() {
-// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
-// manager.create(heartOfGold);
-// heartOfGold.setDescription("prototype ship");
-//
-// thrown.expect(UnauthorizedException.class);
-// manager.modify(heartOfGold);
-// }
-//
-// @Test(expected = NotFoundException.class)
-// public void testModifyNotFound(){
-// manager.modify(createRepositoryWithId());
-// }
-//
-// @Test
-// public void testRefresh() {
-// Repository heartOfGold = createTestRepository();
-// String description = heartOfGold.getDescription();
-//
-// heartOfGold.setDescription("prototype ship");
-// manager.refresh(heartOfGold);
-// assertEquals(description, heartOfGold.getDescription());
-// }
-//
-// @Test
-// @SubjectAware(username = "crato")
-// public void testRefreshWithoutRequiredPermissions() {
-// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
-// manager.create(heartOfGold);
-// heartOfGold.setDescription("prototype ship");
-//
-// thrown.expect(UnauthorizedException.class);
-// manager.refresh(heartOfGold);
-// }
-//
-// @Test(expected = NotFoundException.class)
-// public void testRefreshNotFound(){
-// manager.refresh(createRepositoryWithId());
-// }
-//
-// @Test
-// public void testRepositoryHook() {
-// CountingReceiveHook hook = new CountingReceiveHook();
-// RepositoryManager repoManager = createRepositoryManager(false);
-//
-// ScmEventBus.getInstance().register(hook);
-//
-// assertEquals(0, hook.eventsReceived);
-//
-// Repository repository = createTestRepository();
-// HookContext ctx = createHookContext(repository);
-//
-// repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
-// RepositoryHookType.POST_RECEIVE));
-// assertEquals(1, hook.eventsReceived);
-// repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
-// RepositoryHookType.POST_RECEIVE));
-// assertEquals(2, hook.eventsReceived);
-// }
-//
-// @Test
-// public void testNamespaceSet() {
-// RepositoryManager repoManager = createRepositoryManager(false);
-// Repository repository = spy(createTestRepository());
-// repository.setName("Testrepo");
-// repoManager.create(repository);
-// assertEquals("default_namespace", repository.getNamespace());
-// }
-//
-// @Test
-// public void shouldSetNamespace() {
-// Repository repository = new Repository(null, "hg", null, "scm");
-// manager.create(repository);
-// assertNotNull(repository.getId());
-// assertNotNull(repository.getNamespace());
-// }
-//
-// //~--- methods --------------------------------------------------------------
-//
-// @Override
-// protected DefaultRepositoryManager createManager() {
-// return createRepositoryManager(false);
-// }
-//
-// private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled) {
-// return createRepositoryManager(archiveEnabled, new DefaultKeyGenerator());
-// }
-//
-// private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled, KeyGenerator keyGenerator) {
-// DefaultFileSystem fileSystem = new DefaultFileSystem();
-// Set handlerSet = new HashSet<>();
-// PathBasedRepositoryLocationResolver repositoryLocationResolver = mock(PathBasedRepositoryLocationResolver.class, RETURNS_DEEP_STUBS);
-// when(repositoryLocationResolver.forClass(Path.class).getLocation(anyString())).thenReturn(Paths.get("."));
-// XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, repositoryLocationResolver, fileSystem);
-// ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver);
-// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver));
-// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
-// @Override
-// public RepositoryType getType() {
-// return new RepositoryType("hg", "Mercurial", Sets.newHashSet());
-// }
-// });
-// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
-// @Override
-// public RepositoryType getType() {
-// return new RepositoryType("git", "Git", Sets.newHashSet());
-// }
-// });
-//
-//
-// this.configuration = new ScmConfiguration();
-//
-// configuration.setEnableRepositoryArchive(archiveEnabled);
-//
-// NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
-// when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
-//
-// return new DefaultRepositoryManager(configuration, contextProvider,
-// keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy));
-// }
-//
-// private HookContext createHookContext(Repository repository) {
-// PreProcessorUtil ppu = mock(PreProcessorUtil.class);
-// HookContextProvider provider = mock(HookContextProvider.class);
-// Set features = ImmutableSet.of();
-//
-// when(provider.getSupportedFeatures()).thenReturn(features);
-//
-// return new HookContextFactory(ppu).createContext(provider, repository);
-// }
-//
-// private void assertRepositoriesEquals(Repository repo, Repository other) {
-// assertEquals(repo.getId(), other.getId());
-// assertEquals(repo.getName(), other.getName());
-// assertEquals(repo.getDescription(), other.getDescription());
-// assertEquals(repo.getContact(), other.getContact());
-// assertEquals(repo.getCreationDate(), other.getCreationDate());
-// assertEquals(repo.getLastModified(), other.getLastModified());
-// }
-//
-// private Repository createRepository(Repository repository) {
-// manager.create(repository);
-// assertNotNull(repository.getId());
-// assertNotNull(manager.get(repository.getId()));
-// assertTrue(repository.getCreationDate() > 0);
-//
-// return repository;
-// }
-//
-// private Repository createRepositoryWithId() {
-// Repository repository = RepositoryTestData.createHeartOfGold();
-// repository.setId("abc");
-// return repository;
-// }
-//
-// private Repository createSecondTestRepository() {
-// return createRepository(
-// RepositoryTestData.createHappyVerticalPeopleTransporter());
-// }
-//
-// private Repository createTestRepository() {
-// return createRepository(RepositoryTestData.createHeartOfGold());
-// }
-//
-// private void delete(Manager manager, Repository repository){
-//
-// String id = repository.getId();
-//
-// manager.delete(repository);
-// assertNull(manager.get(id));
-// }
-//
-// private static class CountingReceiveHook {
-//
-// private int eventsReceived = 0;
-//
-// @Subscribe(async = false)
-// public void onEvent(PostReceiveRepositoryHookEvent event) {
-// eventsReceived++;
-// }
-//
-// @Subscribe(async = false)
-// public void onEvent(PreReceiveRepositoryHookEvent event) {
-// eventsReceived++;
-// }
-// }
-//
-// private class TestListener {
-//
-// private HandlerEventType postEvent;
-//
-// private Repository postRepository;
-//
-// private HandlerEventType preEvent;
-//
-// private Repository preRepository;
-//
-// @Subscribe(async = false)
-// public void onEvent(RepositoryEvent event) {
-// if (event.getEventType().isPost()) {
-// this.postRepository = event.getItem();
-// this.postEvent = event.getEventType();
-// }
-// else if (event.getEventType().isPre()) {
-// this.preRepository = event.getItem();
-// this.preEvent = event.getEventType();
-// }
-// }
-// }
-//
}
From f8640d300e07b246152169cd48955893a4596bb4 Mon Sep 17 00:00:00 2001
From: Eduard Heimbuch
Date: Mon, 29 Jun 2020 10:41:31 +0200
Subject: [PATCH 07/20] enhance NamespaceStrategy API / improve frontend
validation and use namespace extension point in RenameRepository component
---
lerna.json | 2 +-
.../scm/repository/NamespaceStrategy.java | 10 ++-
.../repos/components/form/RepositoryForm.tsx | 7 +-
.../src/repos/containers/DangerZone.tsx | 13 ++-
.../src/repos/containers/EditRepo.tsx | 13 ++-
.../src/repos/containers/RenameRepository.tsx | 81 ++++++++++++-------
scm-ui/ui-webapp/src/repos/modules/repos.ts | 3 +-
.../RepositoryToRepositoryDtoMapper.java | 15 +++-
.../CurrentYearNamespaceStrategy.java | 5 ++
.../repository/CustomNamespaceStrategy.java | 5 ++
.../RepositoryTypeNamespaceStrategy.java | 7 +-
.../repository/UsernameNamespaceStrategy.java | 7 +-
.../NamespaceStrategyResourceTest.java | 19 ++++-
.../resources/RepositoryRootResourceTest.java | 8 ++
.../RepositoryToRepositoryDtoMapperTest.java | 11 ++-
.../NamespaceStrategyProviderTest.java | 19 ++++-
.../NamespaceStrategyValidatorTest.java | 9 ++-
17 files changed, 178 insertions(+), 56 deletions(-)
diff --git a/lerna.json b/lerna.json
index ffa2cdc5ad..e17c5bcb57 100644
--- a/lerna.json
+++ b/lerna.json
@@ -5,5 +5,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
- "version": "2.1.1"
+ "version": "2.2.0-SNAPSHOT"
}
diff --git a/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java b/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java
index 4cb7cc32d9..48b3cd09b6 100644
--- a/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java
+++ b/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
import sonia.scm.plugin.ExtensionPoint;
@@ -36,8 +36,14 @@ public interface NamespaceStrategy {
* Create new namespace for the given repository.
*
* @param repository repository
- *
* @return namespace
*/
String createNamespace(Repository repository);
+
+ /**
+ * Checks if the namespace can be changed when using this namespace strategy
+ *
+ * @return namespace can be changed
+ */
+ boolean canBeChanged();
}
diff --git a/scm-ui/ui-webapp/src/repos/components/form/RepositoryForm.tsx b/scm-ui/ui-webapp/src/repos/components/form/RepositoryForm.tsx
index 07e0b276b7..e21ea133bc 100644
--- a/scm-ui/ui-webapp/src/repos/components/form/RepositoryForm.tsx
+++ b/scm-ui/ui-webapp/src/repos/components/form/RepositoryForm.tsx
@@ -26,8 +26,9 @@ import styled from "styled-components";
import { WithTranslation, withTranslation } from "react-i18next";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { Repository, RepositoryType } from "@scm-manager/ui-types";
-import { Checkbox, Level, InputField, Select, SubmitButton, Subtitle, Textarea } from "@scm-manager/ui-components";
+import { Checkbox, InputField, Level, Select, SubmitButton, Subtitle, Textarea } from "@scm-manager/ui-components";
import * as validator from "./repositoryValidation";
+import { CUSTOM_NAMESPACE_STRATEGY } from "../../modules/repos";
const CheckboxWrapper = styled.div`
margin-top: 2em;
@@ -59,8 +60,6 @@ type State = {
contactValidationError: boolean;
};
-const CUSTOM_NAMESPACE_STRATEGY = "CustomNamespaceStrategy";
-
class RepositoryForm extends React.Component {
constructor(props: Props) {
super(props);
@@ -108,7 +107,7 @@ class RepositoryForm extends React.Component {
);
};
- submit = (event: Event) => {
+ submit = (event: React.FormEvent) => {
event.preventDefault();
if (this.isValid()) {
this.props.submitForm(this.state.repository, this.state.initRepository);
diff --git a/scm-ui/ui-webapp/src/repos/containers/DangerZone.tsx b/scm-ui/ui-webapp/src/repos/containers/DangerZone.tsx
index 97cd60b68d..5fb14e4557 100644
--- a/scm-ui/ui-webapp/src/repos/containers/DangerZone.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/DangerZone.tsx
@@ -23,7 +23,7 @@
*/
import React, { FC } from "react";
-import { Repository } from "@scm-manager/ui-types";
+import { Repository, Links } from "@scm-manager/ui-types";
import RenameRepository from "./RenameRepository";
import DeleteRepo from "./DeleteRepo";
import styled from "styled-components";
@@ -32,6 +32,7 @@ import { useTranslation } from "react-i18next";
type Props = {
repository: Repository;
+ indexLinks: Links;
};
const DangerZoneContainer = styled.div`
@@ -44,17 +45,15 @@ const DangerZoneContainer = styled.div`
}
`;
-const DangerZone: FC = ({ repository }) => {
+const DangerZone: FC = ({ repository, indexLinks }) => {
const [t] = useTranslation("repos");
const dangerZone = [];
- if (repository?._links?.rename) {
- dangerZone.push( );
- }
- if (repository?._links?.renameWithNamespace) {
- dangerZone.push( );
+ if (repository?._links?.rename || repository?._links?.renameWithNamespace) {
+ dangerZone.push( );
}
if (repository?._links?.delete) {
+ // @ts-ignore
dangerZone.push( );
}
diff --git a/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx b/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
index 7b6a71b51c..e4d16dcfa8 100644
--- a/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/EditRepo.tsx
@@ -25,17 +25,19 @@ import React from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import RepositoryForm from "../components/form";
-import { Repository } from "@scm-manager/ui-types";
+import { Repository, Links } from "@scm-manager/ui-types";
import { getModifyRepoFailure, isModifyRepoPending, modifyRepo, modifyRepoReset } from "../modules/repos";
import { History } from "history";
import { ErrorNotification } from "@scm-manager/ui-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { compose } from "redux";
import DangerZone from "./DangerZone";
+import { getLinks } from "../../modules/indexResource";
type Props = {
loading: boolean;
error: Error;
+ indexLinks: Links;
modifyRepo: (p1: Repository, p2: () => void) => void;
modifyRepoReset: (p: Repository) => void;
@@ -69,7 +71,7 @@ class EditRepo extends React.Component {
};
render() {
- const { loading, error, repository } = this.props;
+ const { loading, error, repository, indexLinks } = this.props;
const url = this.matchedUrl();
@@ -89,7 +91,7 @@ class EditRepo extends React.Component {
}}
/>
-
+
>
);
}
@@ -99,9 +101,12 @@ const mapStateToProps = (state: any, ownProps: Props) => {
const { namespace, name } = ownProps.repository;
const loading = isModifyRepoPending(state, namespace, name);
const error = getModifyRepoFailure(state, namespace, name);
+ const indexLinks = getLinks(state);
+
return {
loading,
- error
+ error,
+ indexLinks
};
};
diff --git a/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx b/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
index 5209d68f61..e9bce9767e 100644
--- a/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
@@ -22,29 +22,22 @@
* SOFTWARE.
*/
-import React, { FC, useState } from "react";
-import { Repository, Link } from "@scm-manager/ui-types";
-import { CONTENT_TYPE } from "../modules/repos";
-import {
- ErrorNotification,
- Level,
- Button,
- Loading,
- Modal,
- InputField,
- validation,
- ButtonGroup
-} from "@scm-manager/ui-components";
+import React, { FC, useEffect, useState } from "react";
+import { Link, Links, Repository } from "@scm-manager/ui-types";
+import { CONTENT_TYPE, CUSTOM_NAMESPACE_STRATEGY } from "../modules/repos";
+import { Button, ButtonGroup, ErrorNotification, InputField, Level, Loading, Modal } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import { apiClient } from "@scm-manager/ui-components/src";
import { useHistory } from "react-router-dom";
+import { ExtensionPoint } from "@scm-manager/ui-extensions/src";
+import * as validator from "../components/form/repositoryValidation";
type Props = {
repository: Repository;
- renameNamespace: boolean;
+ indexLinks: Links;
};
-const RenameRepository: FC = ({ repository, renameNamespace }) => {
+const RenameRepository: FC = ({ repository, indexLinks }) => {
let history = useHistory();
const [t] = useTranslation("repos");
const [error, setError] = useState(undefined);
@@ -52,6 +45,17 @@ const RenameRepository: FC = ({ repository, renameNamespace }) => {
const [showModal, setShowModal] = useState(false);
const [name, setName] = useState(repository.name);
const [namespace, setNamespace] = useState(repository.namespace);
+ const [nameValidationError, setNameValidationError] = useState(false);
+ const [namespaceValidationError, setNamespaceValidationError] = useState(false);
+ const [currentNamespaceStrategie, setCurrentNamespaceStrategy] = useState("");
+
+ useEffect(() => {
+ apiClient
+ .get((indexLinks?.namespaceStrategies as Link).href)
+ .then(result => result.json())
+ .then(result => setCurrentNamespaceStrategy(result.current))
+ .catch(setError);
+ }, [repository]);
if (error) {
return ;
@@ -62,13 +66,40 @@ const RenameRepository: FC = ({ repository, renameNamespace }) => {
}
const isValid =
- validation.isNameValid(name) &&
- validation.isNameValid(namespace) &&
+ !nameValidationError &&
+ !namespaceValidationError &&
(repository.name !== name || repository.namespace !== namespace);
+ const handleNamespaceChange = (namespace: string) => {
+ setNamespaceValidationError(!validator.isNameValid(namespace));
+ setNamespace(namespace);
+ };
+
+ const handleNameChange = (name: string) => {
+ setNameValidationError(!validator.isNameValid(name));
+ setName(name);
+ };
+
+ const renderNamespaceField = () => {
+ const props = {
+ label: t("repository.namespace"),
+ helpText: t("help.namespaceHelpText"),
+ value: namespace,
+ onChange: handleNamespaceChange,
+ errorMessage: t("validation.namespace-invalid"),
+ validationError: namespaceValidationError
+ };
+
+ if (currentNamespaceStrategie === CUSTOM_NAMESPACE_STRATEGY) {
+ return ;
+ }
+
+ return ;
+ };
+
const rename = () => {
setLoading(true);
- const url = renameNamespace
+ const url = repository?._links?.renameWithNamespace
? (repository?._links?.renameWithNamespace as Link).href
: (repository?._links?.rename as Link).href;
@@ -84,17 +115,13 @@ const RenameRepository: FC = ({ repository, renameNamespace }) => {
- {renameNamespace && (
-
- )}
+ {renderNamespaceField()}
);
diff --git a/scm-ui/ui-webapp/src/repos/modules/repos.ts b/scm-ui/ui-webapp/src/repos/modules/repos.ts
index b9ea59b069..2cc46f033d 100644
--- a/scm-ui/ui-webapp/src/repos/modules/repos.ts
+++ b/scm-ui/ui-webapp/src/repos/modules/repos.ts
@@ -27,7 +27,6 @@ import * as types from "../../modules/types";
import { Action, Repository, RepositoryCollection } from "@scm-manager/ui-types";
import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure";
-import React from "react";
export const FETCH_REPOS = "scm/repos/FETCH_REPOS";
export const FETCH_REPOS_PENDING = `${FETCH_REPOS}_${types.PENDING_SUFFIX}`;
@@ -58,6 +57,8 @@ export const DELETE_REPO_FAILURE = `${DELETE_REPO}_${types.FAILURE_SUFFIX}`;
export const CONTENT_TYPE = "application/vnd.scmm-repository+json;v=2";
+export const CUSTOM_NAMESPACE_STRATEGY = "CustomNamespaceStrategy";
+
// fetch repos
const SORT_BY = "sortBy=namespaceAndName";
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java
index 3de24d64a7..31901e4259 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapper.java
@@ -32,6 +32,7 @@ import org.mapstruct.ObjectFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.Feature;
import sonia.scm.repository.HealthCheckFailure;
+import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.api.Command;
@@ -43,6 +44,7 @@ import sonia.scm.web.api.RepositoryToHalMapper;
import javax.inject.Inject;
import java.util.List;
+import java.util.Set;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link;
@@ -60,6 +62,8 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper strategies;
abstract HealthCheckFailureDto toDto(HealthCheckFailure failure);
@@ -76,7 +80,7 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper allStrategies() {
- return strategies(new AwesomeNamespaceStrategy(), new SuperNamespaceStrategy(), new MegaNamespaceStrategy());
+ return strategies(new AwesomeNamespaceStrategy(), new SuperNamespaceStrategy(), new MegaNamespaceStrategy());
}
private Set strategies(NamespaceStrategy... strategies) {
@@ -80,6 +80,11 @@ class NamespaceStrategyResourceTest {
public String createNamespace(Repository repository) {
return "awesome";
}
+
+ @Override
+ public boolean canBeChanged() {
+ return false;
+ }
}
private static class SuperNamespaceStrategy implements NamespaceStrategy {
@@ -87,6 +92,11 @@ class NamespaceStrategyResourceTest {
public String createNamespace(Repository repository) {
return "super";
}
+
+ @Override
+ public boolean canBeChanged() {
+ return false;
+ }
}
private static class MegaNamespaceStrategy implements NamespaceStrategy {
@@ -94,5 +104,10 @@ class NamespaceStrategyResourceTest {
public String createNamespace(Repository repository) {
return "mega";
}
+
+ @Override
+ public boolean canBeChanged() {
+ return false;
+ }
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
index a6b34b3fc4..3c958cae74 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java
@@ -26,6 +26,7 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
+import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
@@ -40,7 +41,9 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.PageResult;
import sonia.scm.config.ScmConfiguration;
+import sonia.scm.repository.CustomNamespaceStrategy;
import sonia.scm.repository.NamespaceAndName;
+import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryInitializer;
import sonia.scm.repository.RepositoryManager;
@@ -56,6 +59,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.util.Set;
import java.util.function.Predicate;
import static java.util.Collections.singletonList;
@@ -72,6 +76,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -105,6 +110,8 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
private RepositoryInitializer repositoryInitializer;
@Mock
private ScmConfiguration configuration;
+ @Mock
+ private Set strategies;
@Captor
private ArgumentCaptor> filterCaptor;
@@ -129,6 +136,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(scmPathInfoStore.get()).thenReturn(uriInfo);
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
+ doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
trillian.add(new User("trillian"), REALM);
shiro.setSubject(
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java
index f1ffb2eda0..34a49ccbda 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java
@@ -21,11 +21,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
+import com.google.common.collect.ImmutableSet;
import org.apache.shiro.util.ThreadContext;
import org.junit.After;
import org.junit.Before;
@@ -34,7 +35,9 @@ import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.config.ScmConfiguration;
+import sonia.scm.repository.CustomNamespaceStrategy;
import sonia.scm.repository.HealthCheckFailure;
+import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
@@ -42,13 +45,16 @@ import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.ScmProtocol;
import java.net.URI;
+import java.util.Set;
+import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static java.util.stream.Stream.of;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -75,6 +81,8 @@ public class RepositoryToRepositoryDtoMapperTest {
private ScmPathInfo uriInfo;
@Mock
private ScmConfiguration configuration;
+ @Mock
+ private Set strategies;
@InjectMocks
private RepositoryToRepositoryDtoMapperImpl mapper;
@@ -88,6 +96,7 @@ public class RepositoryToRepositoryDtoMapperTest {
when(scmPathInfoStore.get()).thenReturn(uriInfo);
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
+ doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
}
@After
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyProviderTest.java b/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyProviderTest.java
index 7f518254c4..da6bc1c908 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyProviderTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyProviderTest.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
import org.junit.jupiter.api.Test;
@@ -66,12 +66,17 @@ class NamespaceStrategyProviderTest {
return new LinkedHashSet<>(Arrays.asList(new Trillian(), new Zaphod(), new Arthur()));
}
- private static class Trillian implements NamespaceStrategy{
+ private static class Trillian implements NamespaceStrategy {
@Override
public String createNamespace(Repository repository) {
return "trillian";
}
+
+ @Override
+ public boolean canBeChanged() {
+ return false;
+ }
}
private static class Zaphod implements NamespaceStrategy {
@@ -80,6 +85,11 @@ class NamespaceStrategyProviderTest {
public String createNamespace(Repository repository) {
return "zaphod";
}
+
+ @Override
+ public boolean canBeChanged() {
+ return false;
+ }
}
private static class Arthur implements NamespaceStrategy {
@@ -88,6 +98,11 @@ class NamespaceStrategyProviderTest {
public String createNamespace(Repository repository) {
return "arthur";
}
+
+ @Override
+ public boolean canBeChanged() {
+ return false;
+ }
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyValidatorTest.java b/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyValidatorTest.java
index 9f2f5b7709..7de644468e 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyValidatorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyValidatorTest.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
import com.google.common.collect.Sets;
@@ -30,7 +30,7 @@ import sonia.scm.ScmConstraintViolationException;
import java.util.Collections;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
class NamespaceStrategyValidatorTest {
@@ -52,6 +52,11 @@ class NamespaceStrategyValidatorTest {
public String createNamespace(Repository repository) {
return null;
}
+
+ @Override
+ public boolean canBeChanged() {
+ return false;
+ }
}
}
From 64df54e6011af7f976549b9da41f5f0d318ab6e0 Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra
Date: Mon, 29 Jun 2020 13:57:43 +0200
Subject: [PATCH 08/20] fixes broken migration with an empty security.xml
---
CHANGELOG.md | 1 +
.../security/XmlSecurityV1UpdateStep.java | 5 ++--
.../security/XmlSecurityV1UpdateStepTest.java | 25 +++++++++++++----
.../scm/update/security/emptySecurityV1.xml | 28 +++++++++++++++++++
4 files changed, 52 insertions(+), 7 deletions(-)
create mode 100644 scm-webapp/src/test/resources/sonia/scm/update/security/emptySecurityV1.xml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09947917d0..5d101790c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed installation of debian packages on distros without preinstalled `at` ([#1216](https://github.com/scm-manager/scm-manager/issues/1216) and [#1217](https://github.com/scm-manager/scm-manager/pull/1217))
+- Fixed broken migration with empty security.xml ([#1219](https://github.com/scm-manager/scm-manager/issues/1219) and [#1221](https://github.com/scm-manager/scm-manager/pull/1221))
## [2.1.1] - 2020-06-23
### Fixed
diff --git a/scm-webapp/src/main/java/sonia/scm/update/security/XmlSecurityV1UpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/security/XmlSecurityV1UpdateStep.java
index 11d90712d1..11a4a17fac 100644
--- a/scm-webapp/src/main/java/sonia/scm/update/security/XmlSecurityV1UpdateStep.java
+++ b/scm-webapp/src/main/java/sonia/scm/update/security/XmlSecurityV1UpdateStep.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.update.security;
import org.slf4j.Logger;
@@ -44,6 +44,7 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.File;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -179,7 +180,7 @@ public class XmlSecurityV1UpdateStep implements UpdateStep {
@XmlRootElement(name = "configuration")
private static class V1Security {
@XmlElement(name = "entry")
- private List entries;
+ private List entries = new ArrayList<>();
}
@XmlAccessorType(XmlAccessType.FIELD)
diff --git a/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java
index 559b181199..9dc506e919 100644
--- a/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/update/security/XmlSecurityV1UpdateStepTest.java
@@ -43,6 +43,7 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
+import java.util.Map;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
@@ -107,15 +108,17 @@ class XmlSecurityV1UpdateStepTest {
@Nested
class WithExistingSecurityXml {
+ private Path configDir;
+
@BeforeEach
void createSecurityV1XML(@TempDir Path tempDir) throws IOException {
- Path configDir = tempDir.resolve("config");
+ configDir = tempDir.resolve("config");
Files.createDirectories(configDir);
- copyTestDatabaseFile(configDir, "securityV1.xml");
}
@Test
- void shouldMapV1PermissionsFromSecurityV1XML() throws JAXBException {
+ void shouldMapV1PermissionsFromSecurityV1XML() throws IOException, JAXBException {
+ copyTestDatabaseFile(configDir, "securityV1.xml");
updateStep.doUpdate();
List assignedPermission =
assignedPermissionStore.getAll().values()
@@ -127,15 +130,27 @@ class XmlSecurityV1UpdateStepTest {
assertThat(assignedPermission).contains("test");
}
+ @Test
+ void shouldNotFailOnEmptyV1SecurityXml() throws IOException, JAXBException {
+ copyTestDatabaseFile(configDir, "emptySecurityV1.xml", "securityV1.xml");
+ updateStep.doUpdate();
+ assertThat(assignedPermissionStore.getAll()).isEmpty();
+ }
+
}
private void copyTestDatabaseFile(Path configDir, String fileName) throws IOException {
- URL url = Resources.getResource("sonia/scm/update/security/" + fileName);
- Files.copy(url.openStream(), configDir.resolve(fileName));
+ copyTestDatabaseFile(configDir, fileName, fileName);
+ }
+
+ private void copyTestDatabaseFile(Path configDir, String sourceFileName, String targetFileName) throws IOException {
+ URL url = Resources.getResource("sonia/scm/update/security/" + sourceFileName);
+ Files.copy(url.openStream(), configDir.resolve(targetFileName));
}
@Test
void shouldNotFailForMissingConfigDir() throws JAXBException {
updateStep.doUpdate();
+ assertThat(assignedPermissionStore.getAll()).isEmpty();
}
}
diff --git a/scm-webapp/src/test/resources/sonia/scm/update/security/emptySecurityV1.xml b/scm-webapp/src/test/resources/sonia/scm/update/security/emptySecurityV1.xml
new file mode 100644
index 0000000000..c6c16aee28
--- /dev/null
+++ b/scm-webapp/src/test/resources/sonia/scm/update/security/emptySecurityV1.xml
@@ -0,0 +1,28 @@
+
+
+
+
From 4438ff712a69d353a9d8ff95fdc9997e9d1f5777 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Tue, 30 Jun 2020 08:01:06 +0200
Subject: [PATCH 09/20] Fix links
---
README.md | 32 +++++++++++++++-----------------
1 file changed, 15 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index 543956310a..18836ccfd7 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,8 @@ repositories over http.
- Very easy installation
- No need to hack configuration files, SCM-Manager is completely
- configureable from its Web-Interface
-- No Apache and no database installation is required
+ configurable from its Web-Interface
+- No Apache and no database installation required
- Central user, group and permission management
- Out of the box support for Git, Mercurial and Subversion
- Full RESTFul Web Service API (JSON and XML)
@@ -19,34 +19,32 @@ repositories over http.
- Useful plugins available
- Licensed under the MIT-License
-This branch (default) is for the development of SCM-Manager 2.x. If you are interested in the development of version 1.x, please checkout the 1.x branch.
+This branch (`default`) is for the development of SCM-Manager 2.x. If you are interested in the development of version
+1.x, please checkout the branch `support/1.x`.
## News
-- **2018-09-25** - [SCM-Manager 2 gets a boost by Cloudogu
- GmbH](https://www.scm-manager.org/scm-manager-2/scm-manager-2-gets-a-boost-by-cloudogu-gmbh/)
-- **2018-05-04** - SCM-Manager 1.60 released
- ([download](http://www.scm-manager.org/download/) \|
- [release notes](release-notes.md))
-- **2018-04-11** - SCM-Manager 1.59 released
-
-[All news](http://www.scm-manager.org/news/)
+All news regarding SCM-Manager will be published in our [blog](https://www.scm-manager.org/blog/).
## Mailing List
- -
[archive](http://groups.google.com/group/scmmanager) \|
- [subscribe](mailto:scmmanager+subscribe@googlegroups.com)
- \|
+ [subscribe](mailto:scmmanager+subscribe@googlegroups.com) \|
[unsubscribe](mailto:scmmanager+unsubscribe@googlegroups.com)
## Documentation
-You can find the complete documentation in the [docs/](docs/Home.md) directory.
+You can find the complete documentation on our [homepage](https://www.scm-manager.org/docs/).
## Need help?
-Looking for more guidance? Full documentation lives [in the SCM-Manager repository](https://github.com/scm-manager/scm-manager/blob/develop/docs/Home.md). Do you have further ideas or need support?
+Looking for more guidance? Full documentation lives on our [homepage](https://www.scm-manager.org/docs/) or the
+dedicated pages for our [plugins](https://www.scm-manager.org/plugins/). Do you have further ideas or need support?
-- **Community Support** - Contact the SCM-Manager support team for questions about SCM-Manager, to report bugs or to request features through the official channels. [Find more about this here](https://www.scm-manager.org/support/).
+- **Community Support** - Contact the SCM-Manager support team for questions about SCM-Manager, to report bugs or to
+ request features through the official channels. [Find more about this here](https://www.scm-manager.org/support/).
-- **Enterprise Support** - Do you require support with the integration of SCM-Manager into your processes, with the customization of the tool or simply a service level agreement (SLA)? **Contact our development partner Cloudogu! Their team is looking forward to discussing your individual requirements with you and will be more than happy to give you a quote.** [Request Enterprise Support](https://cloudogu.com/en/scm-manager-enterprise/).
+- **Enterprise Support** - Do you require support with the integration of SCM-Manager into your processes, with the
+ customization of the tool or simply a service level agreement (SLA)? **Contact our development partner Cloudogu!
+ Their team is looking forward to discussing your individual requirements with you and will be more than happy to
+ give you a quote.** [Request Enterprise Support](https://cloudogu.com/en/scm-manager-enterprise/).
From f0aac8db117087d7fc4ed5cea9ca8d3f26190e0c Mon Sep 17 00:00:00 2001
From: Florian Scholdei <45232454+fscholdei@users.noreply.github.com>
Date: Tue, 30 Jun 2020 09:20:11 +0200
Subject: [PATCH 10/20] Correct default develop branch in README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 18836ccfd7..0a7fd405e6 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ repositories over http.
- Useful plugins available
- Licensed under the MIT-License
-This branch (`default`) is for the development of SCM-Manager 2.x. If you are interested in the development of version
+This branch (`develop`) is for the development of SCM-Manager 2.x. If you are interested in the development of version
1.x, please checkout the branch `support/1.x`.
## News
From 8165032e43ed4eb76411547ab542aaa9df9cf9f0 Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra
Date: Tue, 30 Jun 2020 10:55:22 +0200
Subject: [PATCH 11/20] fixes restart on rpm or deb installations
Removes jsvc from rpm and deb package and makes more use of systemd
---
CHANGELOG.md | 1 +
scm-packaging/deb/pom.xml | 36 -----
scm-packaging/deb/src/main/bin/scm-server | 146 ++----------------
.../deb/src/main/fs/etc/default/scm-server | 9 --
.../deb/src/main/fs/etc/scm/logging.xml | 3 +-
.../fs/etc/systemd/system/scm-server.service | 11 +-
scm-packaging/rpm/pom.xml | 41 -----
scm-packaging/rpm/src/main/bin/scm-server | 146 ++----------------
.../rpm/src/main/fs/etc/default/scm-server | 9 --
.../rpm/src/main/fs/etc/scm/logging.xml | 3 +-
.../fs/etc/systemd/system/scm-server.service | 11 +-
.../src/main/scripts/after-installation.sh | 6 +-
12 files changed, 42 insertions(+), 380 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09947917d0..b108cd0f11 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed installation of debian packages on distros without preinstalled `at` ([#1216](https://github.com/scm-manager/scm-manager/issues/1216) and [#1217](https://github.com/scm-manager/scm-manager/pull/1217))
+- Fixed restart with deb or rpm installation ([#1222](https://github.com/scm-manager/scm-manager/issues/1222))
## [2.1.1] - 2020-06-23
### Fixed
diff --git a/scm-packaging/deb/pom.xml b/scm-packaging/deb/pom.xml
index 7a3d0c9b47..960225020e 100644
--- a/scm-packaging/deb/pom.xml
+++ b/scm-packaging/deb/pom.xml
@@ -73,30 +73,6 @@
true
-
- copy-jsvc
- prepare-package
-
- unpack
-
-
-
-
- commons-daemon
- commons-daemon-native
- 1.1.0
- tar.gz
-
-
-
-
-
- **/jsvc-linux-*
- ${project.build.directory}/deb/libexec
- false
- true
-
-
copy-webapp
prepare-package
@@ -208,18 +184,6 @@
-
- directory
- ${project.build.directory}/deb/libexec
-
- perm
- /opt/scm-server/libexec
- root
- scm
- 0755
-
-
-
file
src/main/fs/opt/scm-server/var/webapp/docroot/index.html
diff --git a/scm-packaging/deb/src/main/bin/scm-server b/scm-packaging/deb/src/main/bin/scm-server
index 1321985a59..4adbc09294 100755
--- a/scm-packaging/deb/src/main/bin/scm-server
+++ b/scm-packaging/deb/src/main/bin/scm-server
@@ -18,58 +18,14 @@
# Copyright (c) 2001-2002 The Apache Software Foundation. All rights
# reserved.
-# user used to run the daemon (defaults to current user)
-USER="scm"
-
# extra jvm arguments
EXTRA_JVM_ARGUMENTS="-Djava.awt.headless=true -Dlogback.configurationFile=logging.xml"
BASEDIR="/opt/scm-server"
-# set pid path for jsvc
-PIDFILE="/var/run/scm-server.pid"
-
-# set log dir for jsvc
-LOGDIR="/var/log/scm"
-
# load settings from defaults directory
[ -r /etc/default/scm-server ] && . /etc/default/scm-server
-OS=`uname | tr '[:upper:]' '[:lower:]'`
-ARCH=`uname -m`
-
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-case "$OS" in
- sunos*) OS="solaris"
- ARCH=`uname -p`
- ;;
- cygwin*) cygwin=true ;;
- darwin*) darwin=true
- if [ -z "$JAVA_VERSION" ] ; then
- JAVA_VERSION="CurrentJDK"
- else
- echo "Using Java version: $JAVA_VERSION"
- fi
- if [ -z "$JAVA_HOME" ] ; then
- JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Home
- fi
- ;;
-esac
-
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
- fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
-
# If a specific java binary isn't specified search for the standard 'java' binary
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
@@ -97,96 +53,12 @@ fi
CLASSPATH=$CLASSPATH_PREFIX:"$BASEDIR"/conf:"$REPO"/*
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
- [ -n "$HOME" ] && HOME=`cygpath --path --windows "$HOME"`
- [ -n "$BASEDIR" ] && BASEDIR=`cygpath --path --windows "$BASEDIR"`
- [ -n "$REPO" ] && REPO=`cygpath --path --windows "$REPO"`
-fi
-
-jsvc=false;
-stop="";
-
-if [ "$1" == "start" ]
-then
- jsvc=true;
-else
- if [ "$1" == "stop" ]
- then
- jsvc=true;
- stop='-stop';
- fi
-fi
-
-USER_ARGUMENT=""
-
-if [ "x$USER" != "x" ]
-then
- USER_ARGUMENT="-user $USER"
-fi
-
-DARWIN_USE_ARCH="false"
-
-if $jsvc; then
-
- JSVCCMD=""
- if [ "$OS" == "darwin" ]; then
- if [ "$DARWIN_USE_ARCH" == "true" ]; then
- JSVCCMD="/usr/bin/arch -arch $ARCH $BASEDIR/libexec/jsvc-$OS"
- else
- JSVCCMD="exec $BASEDIR/libexec/jsvc-$OS"
- fi
- else
- JSVCCMD="exec $BASEDIR/libexec/jsvc-$OS-$ARCH"
- fi
-
- # try to extract JAVA_HOME from JAVACMD
- if [ -z "$JAVA_HOME" ] ; then
- PRG="$JAVACMD"
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
- done
- DIR="$(dirname "$PRG")"
- DIR="$(dirname "$DIR")"
- if [ -d "$DIR" ] ; then
- JAVA_HOME="$DIR"
- fi
- fi
-
- # TODO JVM Arguments
-
- $JSVCCMD -cp "$CLASSPATH" $JAVA_OPTS \
- $EXTRA_JVM_ARGUMENTS $USER_ARGUMENT \
- -outfile "$LOGDIR/scm-server.out" \
- -errfile "$LOGDIR/scm-server.err" \
- -pidfile "$PIDFILE" \
- -jvm server \
- -home "$JAVA_HOME" \
- -Dapp.name="scm-server" \
- -Dapp.pid="$$" \
- -Dapp.repo="$REPO" \
- -Dbasedir="$BASEDIR" \
- $stop sonia.scm.server.ScmServerDaemon \
- "$@"
-
-else
-
- exec "$JAVACMD" $JAVA_OPTS \
- $EXTRA_JVM_ARGUMENTS \
- -classpath "$CLASSPATH" \
- -Dapp.name="scm-server" \
- -Dapp.pid="$$" \
- -Dapp.repo="$REPO" \
- -Dbasedir="$BASEDIR" \
- sonia.scm.server.ScmServerDaemon \
- "$@"
-
-fi
+exec "$JAVACMD" $JAVA_OPTS \
+ $EXTRA_JVM_ARGUMENTS \
+ -classpath "$CLASSPATH" \
+ -Dapp.name="scm-server" \
+ -Dapp.pid="$$" \
+ -Dapp.repo="$REPO" \
+ -Dbasedir="$BASEDIR" \
+ sonia.scm.server.ScmServerDaemon \
+ "$@"
diff --git a/scm-packaging/deb/src/main/fs/etc/default/scm-server b/scm-packaging/deb/src/main/fs/etc/default/scm-server
index 959947d33f..619e61eaf9 100644
--- a/scm-packaging/deb/src/main/fs/etc/default/scm-server
+++ b/scm-packaging/deb/src/main/fs/etc/default/scm-server
@@ -29,21 +29,12 @@ HOST=0.0.0.0
# scm-server port
PORT=8080
-# change user
-USER=scm
-
# home of scm-manager
export SCM_HOME=/var/lib/scm
# force jvm path
# JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
-# path to pid
-PIDFILE=/var/run/scm/scm.pid
-
-# path to log directory
-LOGDIR=/var/log/scm
-
# increase memory
# EXTRA_JVM_ARGUMENTS="$EXTRA_JVM_ARGUMENTS -Xms1g -Xmx1g"
diff --git a/scm-packaging/deb/src/main/fs/etc/scm/logging.xml b/scm-packaging/deb/src/main/fs/etc/scm/logging.xml
index b3f4014265..b048b71c29 100644
--- a/scm-packaging/deb/src/main/fs/etc/scm/logging.xml
+++ b/scm-packaging/deb/src/main/fs/etc/scm/logging.xml
@@ -100,7 +100,8 @@
-
+
+
diff --git a/scm-packaging/deb/src/main/fs/etc/systemd/system/scm-server.service b/scm-packaging/deb/src/main/fs/etc/systemd/system/scm-server.service
index 58ef9de558..e37e8f5ee7 100644
--- a/scm-packaging/deb/src/main/fs/etc/systemd/system/scm-server.service
+++ b/scm-packaging/deb/src/main/fs/etc/systemd/system/scm-server.service
@@ -3,14 +3,19 @@ Description=SCM-Manager Server
After=syslog.target network.target
[Service]
-Type=forking
+Type=simple
User=scm
Group=scm
WorkingDirectory=/opt/scm-server
-ExecStart=/opt/scm-server/bin/scm-server start
-ExecStop=/opt/scm-server/bin/scm-server stop
+ExecStart=/opt/scm-server/bin/scm-server
+Restart=on-failure
+
+# Exit code 143 means that the program received a SIGTERM signal to instruct it to exit,
+# but it did not handle the signal properly.
+# we suppress that warning for now
+SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
diff --git a/scm-packaging/rpm/pom.xml b/scm-packaging/rpm/pom.xml
index fa5743c9c5..6fa1dff083 100644
--- a/scm-packaging/rpm/pom.xml
+++ b/scm-packaging/rpm/pom.xml
@@ -73,30 +73,6 @@
true
-
- copy-jsvc
- prepare-package
-
- unpack
-
-
-
-
- commons-daemon
- commons-daemon-native
- 1.1.0
- tar.gz
-
-
-
-
-
- **/jsvc-linux-*
- ${project.build.directory}/rpm/libexec
- false
- true
-
-
copy-webapp
prepare-package
@@ -163,15 +139,6 @@
scm
0644
-
-
- file
- /opt/scm-server/libexec
-
- root
- scm
- 0755
-
@@ -229,14 +196,6 @@
default
-
- /opt/scm-server/libexec
-
- ${project.build.directory}/rpm/libexec
-
- default
-
-
/opt/scm-server/var/webapp/docroot/index.html
src/main/fs/opt/scm-server/var/webapp/docroot/index.html
diff --git a/scm-packaging/rpm/src/main/bin/scm-server b/scm-packaging/rpm/src/main/bin/scm-server
index 1321985a59..4adbc09294 100755
--- a/scm-packaging/rpm/src/main/bin/scm-server
+++ b/scm-packaging/rpm/src/main/bin/scm-server
@@ -18,58 +18,14 @@
# Copyright (c) 2001-2002 The Apache Software Foundation. All rights
# reserved.
-# user used to run the daemon (defaults to current user)
-USER="scm"
-
# extra jvm arguments
EXTRA_JVM_ARGUMENTS="-Djava.awt.headless=true -Dlogback.configurationFile=logging.xml"
BASEDIR="/opt/scm-server"
-# set pid path for jsvc
-PIDFILE="/var/run/scm-server.pid"
-
-# set log dir for jsvc
-LOGDIR="/var/log/scm"
-
# load settings from defaults directory
[ -r /etc/default/scm-server ] && . /etc/default/scm-server
-OS=`uname | tr '[:upper:]' '[:lower:]'`
-ARCH=`uname -m`
-
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-case "$OS" in
- sunos*) OS="solaris"
- ARCH=`uname -p`
- ;;
- cygwin*) cygwin=true ;;
- darwin*) darwin=true
- if [ -z "$JAVA_VERSION" ] ; then
- JAVA_VERSION="CurrentJDK"
- else
- echo "Using Java version: $JAVA_VERSION"
- fi
- if [ -z "$JAVA_HOME" ] ; then
- JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Home
- fi
- ;;
-esac
-
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
- fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
-
# If a specific java binary isn't specified search for the standard 'java' binary
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
@@ -97,96 +53,12 @@ fi
CLASSPATH=$CLASSPATH_PREFIX:"$BASEDIR"/conf:"$REPO"/*
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
- [ -n "$HOME" ] && HOME=`cygpath --path --windows "$HOME"`
- [ -n "$BASEDIR" ] && BASEDIR=`cygpath --path --windows "$BASEDIR"`
- [ -n "$REPO" ] && REPO=`cygpath --path --windows "$REPO"`
-fi
-
-jsvc=false;
-stop="";
-
-if [ "$1" == "start" ]
-then
- jsvc=true;
-else
- if [ "$1" == "stop" ]
- then
- jsvc=true;
- stop='-stop';
- fi
-fi
-
-USER_ARGUMENT=""
-
-if [ "x$USER" != "x" ]
-then
- USER_ARGUMENT="-user $USER"
-fi
-
-DARWIN_USE_ARCH="false"
-
-if $jsvc; then
-
- JSVCCMD=""
- if [ "$OS" == "darwin" ]; then
- if [ "$DARWIN_USE_ARCH" == "true" ]; then
- JSVCCMD="/usr/bin/arch -arch $ARCH $BASEDIR/libexec/jsvc-$OS"
- else
- JSVCCMD="exec $BASEDIR/libexec/jsvc-$OS"
- fi
- else
- JSVCCMD="exec $BASEDIR/libexec/jsvc-$OS-$ARCH"
- fi
-
- # try to extract JAVA_HOME from JAVACMD
- if [ -z "$JAVA_HOME" ] ; then
- PRG="$JAVACMD"
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
- done
- DIR="$(dirname "$PRG")"
- DIR="$(dirname "$DIR")"
- if [ -d "$DIR" ] ; then
- JAVA_HOME="$DIR"
- fi
- fi
-
- # TODO JVM Arguments
-
- $JSVCCMD -cp "$CLASSPATH" $JAVA_OPTS \
- $EXTRA_JVM_ARGUMENTS $USER_ARGUMENT \
- -outfile "$LOGDIR/scm-server.out" \
- -errfile "$LOGDIR/scm-server.err" \
- -pidfile "$PIDFILE" \
- -jvm server \
- -home "$JAVA_HOME" \
- -Dapp.name="scm-server" \
- -Dapp.pid="$$" \
- -Dapp.repo="$REPO" \
- -Dbasedir="$BASEDIR" \
- $stop sonia.scm.server.ScmServerDaemon \
- "$@"
-
-else
-
- exec "$JAVACMD" $JAVA_OPTS \
- $EXTRA_JVM_ARGUMENTS \
- -classpath "$CLASSPATH" \
- -Dapp.name="scm-server" \
- -Dapp.pid="$$" \
- -Dapp.repo="$REPO" \
- -Dbasedir="$BASEDIR" \
- sonia.scm.server.ScmServerDaemon \
- "$@"
-
-fi
+exec "$JAVACMD" $JAVA_OPTS \
+ $EXTRA_JVM_ARGUMENTS \
+ -classpath "$CLASSPATH" \
+ -Dapp.name="scm-server" \
+ -Dapp.pid="$$" \
+ -Dapp.repo="$REPO" \
+ -Dbasedir="$BASEDIR" \
+ sonia.scm.server.ScmServerDaemon \
+ "$@"
diff --git a/scm-packaging/rpm/src/main/fs/etc/default/scm-server b/scm-packaging/rpm/src/main/fs/etc/default/scm-server
index 4a5d2fe86c..8f882580c4 100644
--- a/scm-packaging/rpm/src/main/fs/etc/default/scm-server
+++ b/scm-packaging/rpm/src/main/fs/etc/default/scm-server
@@ -29,21 +29,12 @@ HOST=0.0.0.0
# scm-server port
PORT=8080
-# change user
-USER=scm
-
# home of scm-manager
export SCM_HOME=/var/lib/scm
# force jvm path
# JAVA_HOME="/usr/lib/jvm/jre-11"
-# path to pid
-PIDFILE=/var/run/scm/scm.pid
-
-# path to log directory
-LOGDIR=/var/log/scm
-
# increase memory
# EXTRA_JVM_ARGUMENTS="$EXTRA_JVM_ARGUMENTS -Xms1g -Xmx1g"
diff --git a/scm-packaging/rpm/src/main/fs/etc/scm/logging.xml b/scm-packaging/rpm/src/main/fs/etc/scm/logging.xml
index b3f4014265..b048b71c29 100644
--- a/scm-packaging/rpm/src/main/fs/etc/scm/logging.xml
+++ b/scm-packaging/rpm/src/main/fs/etc/scm/logging.xml
@@ -100,7 +100,8 @@
-
+
+
diff --git a/scm-packaging/rpm/src/main/fs/etc/systemd/system/scm-server.service b/scm-packaging/rpm/src/main/fs/etc/systemd/system/scm-server.service
index 58ef9de558..e37e8f5ee7 100644
--- a/scm-packaging/rpm/src/main/fs/etc/systemd/system/scm-server.service
+++ b/scm-packaging/rpm/src/main/fs/etc/systemd/system/scm-server.service
@@ -3,14 +3,19 @@ Description=SCM-Manager Server
After=syslog.target network.target
[Service]
-Type=forking
+Type=simple
User=scm
Group=scm
WorkingDirectory=/opt/scm-server
-ExecStart=/opt/scm-server/bin/scm-server start
-ExecStop=/opt/scm-server/bin/scm-server stop
+ExecStart=/opt/scm-server/bin/scm-server
+Restart=on-failure
+
+# Exit code 143 means that the program received a SIGTERM signal to instruct it to exit,
+# but it did not handle the signal properly.
+# we suppress that warning for now
+SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
diff --git a/scm-packaging/rpm/src/main/scripts/after-installation.sh b/scm-packaging/rpm/src/main/scripts/after-installation.sh
index 474eae5df9..671d91fdff 100644
--- a/scm-packaging/rpm/src/main/scripts/after-installation.sh
+++ b/scm-packaging/rpm/src/main/scripts/after-installation.sh
@@ -32,8 +32,8 @@ if [ -d "${WORKDIR}" ]; then
fi
# reload systemd and make service available
-systemctl daemon-reload
+systemctl --system daemon-reload >/dev/null || true
# enable and start the service
-sudo systemctl enable scm-server
-sudo systemctl start scm-server
+systemctl enable scm-server
+systemctl start scm-server
From dac25f54562e8abcf94cea8afc754487e4db2d54 Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra
Date: Tue, 30 Jun 2020 11:13:06 +0200
Subject: [PATCH 12/20] restart service on rpm or deb package upgrade
---
CHANGELOG.md | 1 +
.../deb/src/main/deb/control/postinst | 23 +++++++++++++++----
.../src/main/scripts/after-installation.sh | 18 +++++++++++----
3 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b108cd0f11..cb4d824441 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- enrich commit mentions in markdown viewer by internal links ([#1210](https://github.com/scm-manager/scm-manager/pull/1210))
+- restart service after rpm or deb package upgrade
### Changed
- Checkboxes can now be 'indeterminate' ([#1215](https://github.com/scm-manager/scm-manager/pull/1215))
diff --git a/scm-packaging/deb/src/main/deb/control/postinst b/scm-packaging/deb/src/main/deb/control/postinst
index 0bd5ff952d..0ca852da1f 100644
--- a/scm-packaging/deb/src/main/deb/control/postinst
+++ b/scm-packaging/deb/src/main/deb/control/postinst
@@ -37,7 +37,22 @@ systemctl daemon-reload
# enable and start the service
sudo systemctl enable scm-server
-# we start scm-manager after 5 seconds
-# this is required, because if we install scm-manager with recommend java
-# java is not fully setup if we ran our postint script
-nohup sh -c "sleep 5; systemctl start scm-server" >/dev/null 2>&1 &
+# reload systemd and make service available
+systemctl --system daemon-reload || true
+
+# enable service
+if ! systemctl is-enabled scm-server >/dev/null
+then
+ systemctl enable scm-server
+fi
+
+# start or restart service
+if systemctl is-active scm-server >/dev/null
+then
+ systemctl restart scm-server
+else
+ # we start scm-manager after 5 seconds
+ # this is required, because if we install scm-manager with recommend java
+ # java is not fully setup if we ran our postint script
+ nohup sh -c "sleep 5; systemctl start scm-server" >/dev/null 2>&1 &
+fi
diff --git a/scm-packaging/rpm/src/main/scripts/after-installation.sh b/scm-packaging/rpm/src/main/scripts/after-installation.sh
index 671d91fdff..6bd95afbe0 100644
--- a/scm-packaging/rpm/src/main/scripts/after-installation.sh
+++ b/scm-packaging/rpm/src/main/scripts/after-installation.sh
@@ -32,8 +32,18 @@ if [ -d "${WORKDIR}" ]; then
fi
# reload systemd and make service available
-systemctl --system daemon-reload >/dev/null || true
+systemctl --system daemon-reload || true
-# enable and start the service
-systemctl enable scm-server
-systemctl start scm-server
+# enable service
+if ! systemctl is-enabled scm-server >/dev/null
+then
+ systemctl enable scm-server
+fi
+
+# start or restart service
+if systemctl is-active scm-server >/dev/null
+then
+ systemctl restart scm-server
+else
+ systemctl start scm-server
+fi
From 9f1d1f85ecca6ae8172720cfbdca6f4214f852b0 Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra
Date: Tue, 30 Jun 2020 11:18:32 +0200
Subject: [PATCH 13/20] added pr link to changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb4d824441..be35396499 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed installation of debian packages on distros without preinstalled `at` ([#1216](https://github.com/scm-manager/scm-manager/issues/1216) and [#1217](https://github.com/scm-manager/scm-manager/pull/1217))
-- Fixed restart with deb or rpm installation ([#1222](https://github.com/scm-manager/scm-manager/issues/1222))
+- Fixed restart with deb or rpm installation ([#1222](https://github.com/scm-manager/scm-manager/issues/1222) and [#1227](https://github.com/scm-manager/scm-manager/pull/1227))
## [2.1.1] - 2020-06-23
### Fixed
From 63692d497a32f3fc24bfd926c99b39ccbe5629c6 Mon Sep 17 00:00:00 2001
From: Eduard Heimbuch
Date: Tue, 30 Jun 2020 11:25:36 +0200
Subject: [PATCH 14/20] fix sonar findings
---
.../scm/repository/ChangeNamespaceNotAllowedException.java | 1 +
.../sonia/scm/repository/DefaultRepositoryManagerTest.java | 5 ++---
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/scm-core/src/main/java/sonia/scm/repository/ChangeNamespaceNotAllowedException.java b/scm-core/src/main/java/sonia/scm/repository/ChangeNamespaceNotAllowedException.java
index 783761e5ff..a9d4f8a631 100644
--- a/scm-core/src/main/java/sonia/scm/repository/ChangeNamespaceNotAllowedException.java
+++ b/scm-core/src/main/java/sonia/scm/repository/ChangeNamespaceNotAllowedException.java
@@ -27,6 +27,7 @@ package sonia.scm.repository;
import sonia.scm.BadRequestException;
import sonia.scm.ContextEntry;
+@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
public class ChangeNamespaceNotAllowedException extends BadRequestException {
public ChangeNamespaceNotAllowedException(Repository repository) {
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
index 326b1f59e7..26299a04a8 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
@@ -217,8 +217,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase {
// test for reference
assertNotSame(heartOfGold, heartReference);
heartReference.setDescription("prototype ship");
- assertFalse(
- heartOfGold.getDescription().equals(heartReference.getDescription()));
+ assertNotEquals(heartOfGold.getDescription(), heartReference.getDescription());
}
@Test
@@ -305,7 +304,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase {
Repository hearReference = manager.get(heartOfGold.getId());
assertNotNull(hearReference);
- assertEquals(hearReference.getDescription(), "prototype ship");
+ assertEquals("prototype ship", hearReference.getDescription());
}
@Test
From d26873f9c6366eaddf271a61f761db6a365e64ec Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra
Date: Wed, 1 Jul 2020 07:09:51 +0200
Subject: [PATCH 15/20] adds missing architecture to debian installation
documentation
---
CHANGELOG.md | 1 +
docs/en/installation/debian.md | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d101790c7..7eb88e6c62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed installation of debian packages on distros without preinstalled `at` ([#1216](https://github.com/scm-manager/scm-manager/issues/1216) and [#1217](https://github.com/scm-manager/scm-manager/pull/1217))
- Fixed broken migration with empty security.xml ([#1219](https://github.com/scm-manager/scm-manager/issues/1219) and [#1221](https://github.com/scm-manager/scm-manager/pull/1221))
+- Added missing architecture to debian installation documentation ([#1230](https://github.com/scm-manager/scm-manager/pull/1230))
## [2.1.1] - 2020-06-23
### Fixed
diff --git a/docs/en/installation/debian.md b/docs/en/installation/debian.md
index 79344139b3..fb4e7d0a06 100644
--- a/docs/en/installation/debian.md
+++ b/docs/en/installation/debian.md
@@ -9,7 +9,7 @@ displayToc: true
The following code block will configure an apt repository for scm-manager and install it.
```bash
-echo 'deb https://packages.scm-manager.org/repository/apt-v2-releases/ stable main' | sudo tee /etc/apt/sources.list.d/scm-manager.list
+echo 'deb [arch=all] https://packages.scm-manager.org/repository/apt-v2-releases/ stable main' | sudo tee /etc/apt/sources.list.d/scm-manager.list
sudo apt-key adv --recv-keys --keyserver hkps://keys.openpgp.org 0x975922F193B07D6E
sudo apt-get update
sudo apt-get install scm-server
@@ -24,7 +24,7 @@ To install SCM-Manager as a debian package (.deb), we have to configure an apt r
Create a file at `/etc/apt/sources.list.d/scm-manager.list` with the following content:
```text
-deb https://packages.scm-manager.org/repository/apt-v2-releases/ stable main
+deb [arch=all] https://packages.scm-manager.org/repository/apt-v2-releases/ stable main
```
This will add the apt repository of the scm-manager stable releases to the list of your apt repositories.
From 1f061266e28949fbcd7fb222bf0232a0b719392b Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra
Date: Wed, 1 Jul 2020 06:51:51 +0200
Subject: [PATCH 16/20] update project metadata in order to fix deb and rpm
package information
---
CHANGELOG.md | 1 +
README.md | 2 +-
pom.xml | 35 +++++++++++++++++--
scm-packaging/deb/pom.xml | 1 -
.../deb/src/main/deb/control/control | 5 +--
scm-packaging/rpm/pom.xml | 9 ++++-
6 files changed, 45 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7eb88e6c62..6adb74d02b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed installation of debian packages on distros without preinstalled `at` ([#1216](https://github.com/scm-manager/scm-manager/issues/1216) and [#1217](https://github.com/scm-manager/scm-manager/pull/1217))
- Fixed broken migration with empty security.xml ([#1219](https://github.com/scm-manager/scm-manager/issues/1219) and [#1221](https://github.com/scm-manager/scm-manager/pull/1221))
- Added missing architecture to debian installation documentation ([#1230](https://github.com/scm-manager/scm-manager/pull/1230))
+- Fixed wrong package information for deb and rpm packages ([#1229](https://github.com/scm-manager/scm-manager/pull/1229))
## [2.1.1] - 2020-06-23
### Fixed
diff --git a/README.md b/README.md
index 0a7fd405e6..4386fbe026 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
The easiest way to share and manage your Git, Mercurial and Subversion
-repositories over http.
+repositories.
- Very easy installation
- No need to hack configuration files, SCM-Manager is completely
diff --git a/pom.xml b/pom.xml
index db981176f6..e2a51b1a4d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,12 +35,17 @@
2.2.0-SNAPSHOT
The easiest way to share your Git, Mercurial
- and Subversion repositories over http.
+ and Subversion repositories.
scm
https://github.com/scm-manager/scm-manager
+
+ Cloudogu GmbH
+ https://cloudogu.com
+
+
MIT License
@@ -50,9 +55,33 @@
- sdorra
+ sebastian.sdorra
Sebastian Sdorra
- s.sdorra@gmail.com
+ sebastian.sdorra@cloudogu.com
+ Europe/Berlin
+
+
+ rene.pfeuffer
+ Rene Pfeufer
+ rene.pfeuffer@cloudogu.com
+ Europe/Berlin
+
+
+ eduard.heimbuch
+ Eduard Heimbuch
+ eduard.heimbuch@cloudogu.com
+ Europe/Berlin
+
+
+ florian.scholdei
+ Florian Scholdei
+ florian.scholdei@cloudogu.com
+ Europe/Berlin
+
+
+ Konstantin Schaper
+ Konstantin Schaper
+ konstantin.schaper@cloudogu.com
Europe/Berlin
diff --git a/scm-packaging/deb/pom.xml b/scm-packaging/deb/pom.xml
index 7a3d0c9b47..81da36a3fc 100644
--- a/scm-packaging/deb/pom.xml
+++ b/scm-packaging/deb/pom.xml
@@ -157,7 +157,6 @@
-
file
src/main/fs/etc/scm/logging.xml
diff --git a/scm-packaging/deb/src/main/deb/control/control b/scm-packaging/deb/src/main/deb/control/control
index 06a721b7ff..29926baf5b 100644
--- a/scm-packaging/deb/src/main/deb/control/control
+++ b/scm-packaging/deb/src/main/deb/control/control
@@ -3,7 +3,8 @@ Version: [[version]]
Section: devel
Priority: extra
Architecture: all
-Description: The easiest way to share and manage your Git, Mercurial and Subversion repositories over http
-Maintainer: Sebastian Sdorra
+Description: The easiest way to share and manage your Git, Mercurial and Subversion repositories
+Maintainer: SCM-Team
+Homepage: https://scm-manager.org
Depends: adduser, procps, psmisc, net-tools
Recommends: openjdk-11-jre-headless, mercurial
diff --git a/scm-packaging/rpm/pom.xml b/scm-packaging/rpm/pom.xml
index fa5743c9c5..86ed0c29ff 100644
--- a/scm-packaging/rpm/pom.xml
+++ b/scm-packaging/rpm/pom.xml
@@ -41,6 +41,12 @@
Packaging for RedHat/Centos/Fedora
rpm
+
+ https://scm-manager.org
+
@@ -127,6 +133,8 @@
true
scm-server
+ SCM-Manager Server
+ The easiest way to share and manage your Git, Mercurial and Subversion repositories
Development/Tools
MIT
true
@@ -319,7 +327,6 @@
-
org.apache.maven.plugins
maven-deploy-plugin
From a0d9bacd80750b1bcfa08f808252e58e4b2da677 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Wed, 1 Jul 2020 17:42:30 +0200
Subject: [PATCH 17/20] Use default implementation to keep compatibility
---
.../scm/repository/NamespaceStrategy.java | 4 +++-
.../CurrentYearNamespaceStrategy.java | 5 -----
.../RepositoryTypeNamespaceStrategy.java | 7 +------
.../repository/UsernameNamespaceStrategy.java | 7 +------
.../NamespaceStrategyResourceTest.java | 19 ++-----------------
.../NamespaceStrategyProviderTest.java | 19 ++-----------------
.../NamespaceStrategyValidatorTest.java | 9 ++-------
7 files changed, 11 insertions(+), 59 deletions(-)
diff --git a/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java b/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java
index 48b3cd09b6..4548d2b82b 100644
--- a/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java
+++ b/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategy.java
@@ -45,5 +45,7 @@ public interface NamespaceStrategy {
*
* @return namespace can be changed
*/
- boolean canBeChanged();
+ default boolean canBeChanged() {
+ return false;
+ }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/CurrentYearNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/CurrentYearNamespaceStrategy.java
index 3a23b390fe..ef398c291c 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/CurrentYearNamespaceStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/CurrentYearNamespaceStrategy.java
@@ -50,9 +50,4 @@ public class CurrentYearNamespaceStrategy implements NamespaceStrategy {
public String createNamespace(Repository repository) {
return String.valueOf(Year.now(clock).getValue());
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryTypeNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryTypeNamespaceStrategy.java
index 958e51892d..d378bc7354 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryTypeNamespaceStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryTypeNamespaceStrategy.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
import sonia.scm.plugin.Extension;
@@ -32,9 +32,4 @@ public class RepositoryTypeNamespaceStrategy implements NamespaceStrategy {
public String createNamespace(Repository repository) {
return repository.getType();
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/UsernameNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/UsernameNamespaceStrategy.java
index d4e4742305..9b6cfd21c5 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/UsernameNamespaceStrategy.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/UsernameNamespaceStrategy.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
import org.apache.shiro.SecurityUtils;
@@ -34,9 +34,4 @@ public class UsernameNamespaceStrategy implements NamespaceStrategy {
public String createNamespace(Repository repository) {
return SecurityUtils.getSubject().getPrincipal().toString();
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NamespaceStrategyResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NamespaceStrategyResourceTest.java
index 656dd225cd..9beddd5ff3 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NamespaceStrategyResourceTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/NamespaceStrategyResourceTest.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.api.v2.resources;
import com.google.common.collect.Lists;
@@ -68,7 +68,7 @@ class NamespaceStrategyResourceTest {
}
private Set allStrategies() {
- return strategies(new AwesomeNamespaceStrategy(), new SuperNamespaceStrategy(), new MegaNamespaceStrategy());
+ return strategies(new AwesomeNamespaceStrategy(), new SuperNamespaceStrategy(), new MegaNamespaceStrategy());
}
private Set strategies(NamespaceStrategy... strategies) {
@@ -80,11 +80,6 @@ class NamespaceStrategyResourceTest {
public String createNamespace(Repository repository) {
return "awesome";
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
private static class SuperNamespaceStrategy implements NamespaceStrategy {
@@ -92,11 +87,6 @@ class NamespaceStrategyResourceTest {
public String createNamespace(Repository repository) {
return "super";
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
private static class MegaNamespaceStrategy implements NamespaceStrategy {
@@ -104,10 +94,5 @@ class NamespaceStrategyResourceTest {
public String createNamespace(Repository repository) {
return "mega";
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyProviderTest.java b/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyProviderTest.java
index da6bc1c908..7f518254c4 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyProviderTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyProviderTest.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
import org.junit.jupiter.api.Test;
@@ -66,17 +66,12 @@ class NamespaceStrategyProviderTest {
return new LinkedHashSet<>(Arrays.asList(new Trillian(), new Zaphod(), new Arthur()));
}
- private static class Trillian implements NamespaceStrategy {
+ private static class Trillian implements NamespaceStrategy{
@Override
public String createNamespace(Repository repository) {
return "trillian";
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
private static class Zaphod implements NamespaceStrategy {
@@ -85,11 +80,6 @@ class NamespaceStrategyProviderTest {
public String createNamespace(Repository repository) {
return "zaphod";
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
private static class Arthur implements NamespaceStrategy {
@@ -98,11 +88,6 @@ class NamespaceStrategyProviderTest {
public String createNamespace(Repository repository) {
return "arthur";
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
}
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyValidatorTest.java b/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyValidatorTest.java
index 7de644468e..9f2f5b7709 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyValidatorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/NamespaceStrategyValidatorTest.java
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
+
package sonia.scm.repository;
import com.google.common.collect.Sets;
@@ -30,7 +30,7 @@ import sonia.scm.ScmConstraintViolationException;
import java.util.Collections;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
class NamespaceStrategyValidatorTest {
@@ -52,11 +52,6 @@ class NamespaceStrategyValidatorTest {
public String createNamespace(Repository repository) {
return null;
}
-
- @Override
- public boolean canBeChanged() {
- return false;
- }
}
}
From 846ddd5047b452c4259b0a7a7f4ba03b1fe2a909 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Wed, 1 Jul 2020 17:47:01 +0200
Subject: [PATCH 18/20] Add missing translation
---
scm-webapp/src/main/resources/locales/de/plugins.json | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json
index eb9e390497..e5972f31c9 100644
--- a/scm-webapp/src/main/resources/locales/de/plugins.json
+++ b/scm-webapp/src/main/resources/locales/de/plugins.json
@@ -28,6 +28,12 @@
"description": "Darf alle Repositories lesen, klonen und schreiben."
}
},
+ "read,rename": {
+ "*": {
+ "displayName": "Alle Repositories umbenennen",
+ "description": "Darf alle Repositories lesen und umbenennen."
+ }
+ },
"*": {
"displayName": "Alle Repositories besitzen (Owner)",
"description": "Darf alle Repositories lesen, klonen, schreiben, konfigurieren und löschen."
From 3e0638f8522a7aecd7803c04c5a838787b6329ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Wed, 1 Jul 2020 18:02:32 +0200
Subject: [PATCH 19/20] Fix imports
---
scm-ui/ui-webapp/src/admin/containers/Admin.tsx | 2 +-
.../src/admin/plugins/containers/PluginsOverview.tsx | 2 +-
scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx | 2 +-
scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx | 2 +-
scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx | 4 ++--
.../ui-webapp/src/repos/sources/components/FileTreeLeaf.tsx | 2 +-
6 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/scm-ui/ui-webapp/src/admin/containers/Admin.tsx b/scm-ui/ui-webapp/src/admin/containers/Admin.tsx
index 089cb959fb..51308f5af1 100644
--- a/scm-ui/ui-webapp/src/admin/containers/Admin.tsx
+++ b/scm-ui/ui-webapp/src/admin/containers/Admin.tsx
@@ -44,7 +44,7 @@ import GlobalConfig from "./GlobalConfig";
import RepositoryRoles from "../roles/containers/RepositoryRoles";
import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole";
import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
-import { StateMenuContextProvider } from "@scm-manager/ui-components/src/navigation/MenuContext";
+import { StateMenuContextProvider } from "@scm-manager/ui-components";
type Props = RouteComponentProps &
WithTranslation & {
diff --git a/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx b/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx
index 74a89d3828..7605495810 100644
--- a/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx
+++ b/scm-ui/ui-webapp/src/admin/plugins/containers/PluginsOverview.tsx
@@ -54,7 +54,7 @@ import PluginBottomActions from "../components/PluginBottomActions";
import ExecutePendingActionModal from "../components/ExecutePendingActionModal";
import CancelPendingActionModal from "../components/CancelPendingActionModal";
import UpdateAllActionModal from "../components/UpdateAllActionModal";
-import { Plugin } from "@scm-manager/ui-types/src";
+import { Plugin } from "@scm-manager/ui-types";
import ShowPendingModal from "../components/ShowPendingModal";
type Props = WithTranslation & {
diff --git a/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx
index 0709681199..ceeeae20f8 100644
--- a/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx
+++ b/scm-ui/ui-webapp/src/groups/containers/CreateGroup.tsx
@@ -31,7 +31,7 @@ import { Page } from "@scm-manager/ui-components";
import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource";
import { createGroup, createGroupReset, getCreateGroupFailure, isCreateGroupPending } from "../modules/groups";
import GroupForm from "../components/GroupForm";
-import { apiClient } from "@scm-manager/ui-components/src";
+import { apiClient } from "@scm-manager/ui-components";
type Props = WithTranslation & {
createGroup: (link: string, group: Group, callback?: () => void) => void;
diff --git a/scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx
index df3991cfd0..d1471b59e3 100644
--- a/scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx
+++ b/scm-ui/ui-webapp/src/groups/containers/EditGroup.tsx
@@ -31,7 +31,7 @@ import { DisplayedUser, 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";
+import { apiClient } from "@scm-manager/ui-components";
import { compose } from "redux";
type Props = {
diff --git a/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx b/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
index e9bce9767e..ede6414029 100644
--- a/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
+++ b/scm-ui/ui-webapp/src/repos/containers/RenameRepository.tsx
@@ -27,9 +27,9 @@ import { Link, Links, Repository } from "@scm-manager/ui-types";
import { CONTENT_TYPE, CUSTOM_NAMESPACE_STRATEGY } from "../modules/repos";
import { Button, ButtonGroup, ErrorNotification, InputField, Level, Loading, Modal } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
-import { apiClient } from "@scm-manager/ui-components/src";
+import { apiClient } from "@scm-manager/ui-components";
import { useHistory } from "react-router-dom";
-import { ExtensionPoint } from "@scm-manager/ui-extensions/src";
+import { ExtensionPoint } from "@scm-manager/ui-extensions";
import * as validator from "../components/form/repositoryValidation";
type Props = {
diff --git a/scm-ui/ui-webapp/src/repos/sources/components/FileTreeLeaf.tsx b/scm-ui/ui-webapp/src/repos/sources/components/FileTreeLeaf.tsx
index 184c2e3422..bc953a711d 100644
--- a/scm-ui/ui-webapp/src/repos/sources/components/FileTreeLeaf.tsx
+++ b/scm-ui/ui-webapp/src/repos/sources/components/FileTreeLeaf.tsx
@@ -29,7 +29,7 @@ import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
import { File } from "@scm-manager/ui-types";
import { DateFromNow, FileSize, Tooltip } from "@scm-manager/ui-components";
import FileIcon from "./FileIcon";
-import { Icon } from "@scm-manager/ui-components/src";
+import { Icon } from "@scm-manager/ui-components";
import { WithTranslation, withTranslation } from "react-i18next";
type Props = WithTranslation & {
From 5baf3bd9c007a7943162ebd13843c41f70f8333a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?=
Date: Wed, 1 Jul 2020 20:25:36 +0200
Subject: [PATCH 20/20] Use namespace strategy to set new namespace
---
.../scm/repository/DefaultRepositoryManager.java | 14 +++++++-------
.../repository/DefaultRepositoryManagerTest.java | 15 ++++++++-------
2 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java
index db09d23e79..49eeb7c342 100644
--- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java
@@ -246,11 +246,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
public Repository rename(Repository repository, String newNamespace, String newName) {
- if (!configuration.getNamespaceStrategy().equals("CustomNamespaceStrategy")
- && !repository.getNamespace().equals(newNamespace)) {
- throw new ChangeNamespaceNotAllowedException(repository);
- }
-
if (hasNamespaceOrNameNotChanged(repository, newNamespace, newName)) {
throw new NoChangesMadeException(repository);
}
@@ -259,8 +254,13 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
if (!Strings.isNullOrEmpty(newName)) {
changedRepository.setName(newName);
}
- if (!Strings.isNullOrEmpty(newNamespace)) {
- changedRepository.setNamespace(newNamespace);
+
+ if (!Strings.isNullOrEmpty(newNamespace) && !repository.getNamespace().equals(newNamespace)) {
+ NamespaceStrategy strategy = namespaceStrategyProvider.get();
+ if (!strategy.canBeChanged()) {
+ throw new ChangeNamespaceNotAllowedException(repository);
+ }
+ changedRepository.setNamespace(strategy.createNamespace(changedRepository));
}
managerDaoAdapter.modify(
diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
index 26299a04a8..9a49d86bc9 100644
--- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java
@@ -79,7 +79,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -108,6 +107,8 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase {
@Rule
public ExpectedException thrown = ExpectedException.none();
+ private NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
+
private ScmConfiguration configuration;
private String mockedNamespace = "default_namespace";
@@ -390,6 +391,8 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase {
public void shouldThrowChangeNamespaceNotAllowedException() {
Repository repository = new Repository("1", "hg", "space", "x");
RepositoryManager repoManager = createManager();
+ when(namespaceStrategy.canBeChanged()).thenReturn(false);
+
thrown.expect(ChangeNamespaceNotAllowedException.class);
repoManager.rename(repository, "hitchhiker", "heart-of-gold");
@@ -397,7 +400,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase {
@Test
public void shouldThrowNoChangesMadeException() {
- configuration.setNamespaceStrategy("CustomNamespaceStrategy");
Repository repository = new Repository("1", "hg", "space", "x");
RepositoryManager repoManager = createManager();
@@ -410,7 +412,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase {
public void shouldOnlyChangeRepositoryName() {
Repository repository = createTestRepository();
RepositoryManager repoManager = (RepositoryManager) manager;
- configuration.setNamespaceStrategy("UsernameNamespaceStrategy");
Repository changedRepo = repoManager.rename(repository, "default_namespace", "puzzle42");
assertNotEquals(changedRepo.getName(), repository.getName());
@@ -420,11 +421,12 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase {
public void shouldRenameRepositoryNamespaceAndName() {
Repository repository = createTestRepository();
RepositoryManager repoManager = (RepositoryManager) manager;
- configuration.setNamespaceStrategy("CustomNamespaceStrategy");
+ when(namespaceStrategy.canBeChanged()).thenReturn(true);
+ when(namespaceStrategy.createNamespace(any(Repository.class))).thenReturn("hitchhiker");
Repository changedRepo = repoManager.rename(repository, "hitchhiker", "puzzle42");
- assertNotEquals(changedRepo.getName(), repository.getName());
- assertNotEquals(changedRepo.getNamespace(), repository.getNamespace());
+ assertEquals("puzzle42", changedRepo.getName());
+ assertEquals("hitchhiker", changedRepo.getNamespace());
}
//~--- methods --------------------------------------------------------------
@@ -445,7 +447,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase {
this.configuration = new ScmConfiguration();
- NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
return new DefaultRepositoryManager(configuration, contextProvider,