Repository specific mercurial encoding (#1583)

* Use HgRepositoryConfig instead of HgConfig for hg commands

* Rename HgConfig to HgGlobalConfig

* Resolve encoding from repository specific configuration

* Add ui for repository specific configuration

* Validate encoding on dto
This commit is contained in:
Sebastian Sdorra
2021-03-15 08:40:05 +01:00
committed by GitHub
parent ddac6b1e50
commit b339dd068f
73 changed files with 1914 additions and 498 deletions

View File

@@ -0,0 +1,2 @@
- type: added
description: Mercurial encoding configuration per repository ([#1577](https://github.com/scm-manager/scm-manager/issues/1577), [#1583](https://github.com/scm-manager/scm-manager/issues/1583))

View File

@@ -35,6 +35,12 @@ dependencies {
implementation libraries.commonsCompress
testImplementation libraries.shiroUnit
testImplementation libraries.logback
// validation api
testImplementation libraries.validator
testImplementation libraries.elApi
testImplementation libraries.elRuntime
testImplementation libraries.resteasyValidatorProvider
}
scmPlugin {

View File

@@ -0,0 +1,46 @@
/*
* 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 javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
@Target({ FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Constraint(validatedBy = EncodingValidator.class)
public @interface Encoding {
String message() default "Invalid encoding";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}

View File

@@ -0,0 +1,53 @@
/*
* 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 com.google.common.base.Strings;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
public class EncodingValidator implements ConstraintValidator<Encoding, String> {
@Override
public void initialize(Encoding constraintAnnotation) {
// do nothing
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (Strings.isNullOrEmpty(value)) {
return true;
}
try {
Charset.forName(value);
return true;
} catch (UnsupportedCharsetException ex) {
return false;
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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 com.google.common.annotations.VisibleForTesting;
import com.google.inject.util.Providers;
import sonia.scm.repository.Repository;
import javax.inject.Inject;
import javax.inject.Provider;
public class HgConfigLinks {
private final Provider<ScmPathInfoStore> pathInfoStore;
@Inject
public HgConfigLinks(Provider<ScmPathInfoStore> pathInfoStore) {
this.pathInfoStore = pathInfoStore;
}
@VisibleForTesting
public HgConfigLinks(ScmPathInfoStore pathInfoStore) {
this.pathInfoStore = Providers.of(pathInfoStore);
}
public ConfigLinks global() {
LinkBuilder linkBuilder = new LinkBuilder(pathInfoStore.get().get(), HgConfigResource.class);
return new ConfigLinks() {
@Override
public String get() {
return linkBuilder.method("get").parameters().href();
}
@Override
public String update() {
return linkBuilder.method("update").parameters().href();
}
};
}
public ConfigLinks repository(Repository repository) {
return repository(repository.getNamespace(), repository.getName());
}
public ConfigLinks repository(String namespace, String name) {
LinkBuilder linkBuilder = new LinkBuilder(pathInfoStore.get().get(), HgConfigResource.class, HgRepositoryConfigResource.class)
.method("getRepositoryConfigResource")
.parameters();
return new ConfigLinks() {
@Override
public String get() {
return linkBuilder.method("getHgRepositoryConfig").parameters(namespace, name).href();
}
@Override
public String update() {
return linkBuilder.method("updateHgRepositoryConfig").parameters(namespace, name).href();
}
};
}
public interface ConfigLinks {
String get();
String update();
}
}

View File

@@ -33,13 +33,14 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;
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.GET;
import javax.ws.rs.PUT;
@@ -57,20 +58,23 @@ import javax.ws.rs.core.Response;
public class HgConfigResource {
static final String HG_CONFIG_PATH_V2 = "v2/config/hg";
private final HgConfigDtoToHgConfigMapper dtoToConfigMapper;
private final HgConfigToHgConfigDtoMapper configToDtoMapper;
private final HgGlobalConfigDtoToHgConfigMapper dtoToConfigMapper;
private final HgGlobalConfigToHgGlobalConfigDtoMapper configToDtoMapper;
private final HgRepositoryHandler repositoryHandler;
private final Provider<HgConfigAutoConfigurationResource> autoconfigResource;
private final Provider<HgGlobalConfigAutoConfigurationResource> autoconfigResource;
private final Provider<HgRepositoryConfigResource> repositoryConfigResource;
@Inject
public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper,
HgConfigToHgConfigDtoMapper configToDtoMapper,
public HgConfigResource(HgGlobalConfigDtoToHgConfigMapper dtoToConfigMapper,
HgGlobalConfigToHgGlobalConfigDtoMapper configToDtoMapper,
HgRepositoryHandler repositoryHandler,
Provider<HgConfigAutoConfigurationResource> autoconfigResource) {
Provider<HgGlobalConfigAutoConfigurationResource> autoconfigResource,
Provider<HgRepositoryConfigResource> repositoryConfigResource) {
this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper;
this.repositoryHandler = repositoryHandler;
this.autoconfigResource = autoconfigResource;
this.repositoryConfigResource = repositoryConfigResource;
}
/**
@@ -85,7 +89,7 @@ public class HgConfigResource {
description = "success",
content = @Content(
mediaType = HgVndMediaType.CONFIG,
schema = @Schema(implementation = HgConfigDto.class)
schema = @Schema(implementation = HgGlobalGlobalConfigDto.class)
)
)
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
@@ -99,12 +103,12 @@ public class HgConfigResource {
))
public Response get() {
ConfigurationPermissions.read(HgConfig.PERMISSION).check();
ConfigurationPermissions.read(HgGlobalConfig.PERMISSION).check();
HgConfig config = repositoryHandler.getConfig();
HgGlobalConfig config = repositoryHandler.getConfig();
if (config == null) {
config = new HgConfig();
config = new HgGlobalConfig();
repositoryHandler.setConfig(config);
}
@@ -127,7 +131,7 @@ public class HgConfigResource {
requestBody = @RequestBody(
content = @Content(
mediaType = HgVndMediaType.CONFIG,
schema = @Schema(implementation = UpdateHgConfigDto.class),
schema = @Schema(implementation = UpdateHgGlobalConfigDto.class),
examples = @ExampleObject(
name = "Overwrites current configuration with this one.",
value = "{\n \"disabled\":false,\n \"hgBinary\":\"hg\",\n \"encoding\":\"UTF-8\",\n \"showRevisionInId\":false,\n \"enableHttpPostArgs\":false\n}",
@@ -149,9 +153,9 @@ public class HgConfigResource {
mediaType = VndMediaType.ERROR_TYPE,
schema = @Schema(implementation = ErrorDto.class)
))
public Response update(HgConfigDto configDto) {
public Response update(@Valid HgGlobalGlobalConfigDto configDto) {
HgConfig config = dtoToConfigMapper.map(configDto);
HgGlobalConfig config = dtoToConfigMapper.map(configDto);
ConfigurationPermissions.write(config).check();
@@ -162,7 +166,13 @@ public class HgConfigResource {
}
@Path("auto-configuration")
public HgConfigAutoConfigurationResource getAutoConfigurationResource() {
public HgGlobalConfigAutoConfigurationResource getAutoConfigurationResource() {
return autoconfigResource.get();
}
@Path("{namespace}/{name}")
public HgRepositoryConfigResource getRepositoryConfigResource() {
return repositoryConfigResource.get();
}
}

View File

@@ -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.inject.Inject;
@@ -32,7 +32,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;
import sonia.scm.web.VndMediaType;
@@ -42,14 +42,14 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
public class HgConfigAutoConfigurationResource {
public class HgGlobalConfigAutoConfigurationResource {
private final HgRepositoryHandler repositoryHandler;
private final HgConfigDtoToHgConfigMapper dtoToConfigMapper;
private final HgGlobalConfigDtoToHgConfigMapper dtoToConfigMapper;
@Inject
public HgConfigAutoConfigurationResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper,
HgRepositoryHandler repositoryHandler) {
public HgGlobalConfigAutoConfigurationResource(HgGlobalConfigDtoToHgConfigMapper dtoToConfigMapper,
HgRepositoryHandler repositoryHandler) {
this.dtoToConfigMapper = dtoToConfigMapper;
this.repositoryHandler = repositoryHandler;
}
@@ -92,7 +92,7 @@ public class HgConfigAutoConfigurationResource {
requestBody = @RequestBody(
content = @Content(
mediaType = HgVndMediaType.CONFIG,
schema = @Schema(implementation = UpdateHgConfigDto.class),
schema = @Schema(implementation = UpdateHgGlobalConfigDto.class),
examples = @ExampleObject(
name = "Overwrites current configuration with this one and installs the mercurial binary.",
value = "{\n \"disabled\":false,\n \"hgBinary\":\"hg\",\n \"pythonBinary\":\"python\",\n \"pythonPath\":\"\",\n \"encoding\":\"UTF-8\",\n \"useOptimizedBytecode\":false,\n \"showRevisionInId\":false,\n \"disableHookSSLValidation\":false,\n \"enableHttpPostArgs\":false\n}",
@@ -114,14 +114,14 @@ public class HgConfigAutoConfigurationResource {
mediaType = VndMediaType.ERROR_TYPE,
schema = @Schema(implementation = ErrorDto.class)
))
public Response autoConfiguration(HgConfigDto configDto) {
public Response autoConfiguration(HgGlobalGlobalConfigDto configDto) {
HgConfig config;
HgGlobalConfig config;
if (configDto != null) {
config = dtoToConfigMapper.map(configDto);
} else {
config = new HgConfig();
config = new HgGlobalConfig();
}
ConfigurationPermissions.write(config).check();

View File

@@ -21,15 +21,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
import org.mapstruct.Mapper;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class HgConfigDtoToHgConfigMapper {
public abstract HgConfig map(HgConfigDto dto);
public abstract class HgGlobalConfigDtoToHgConfigMapper {
public abstract HgGlobalConfig map(HgGlobalGlobalConfigDto dto);
}

View File

@@ -21,14 +21,14 @@
* 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.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.web.JsonEnricherBase;
import sonia.scm.web.JsonEnricherContext;
@@ -39,19 +39,19 @@ import static java.util.Collections.singletonMap;
import static sonia.scm.web.VndMediaType.INDEX;
@Extension
public class HgConfigInIndexResource extends JsonEnricherBase {
public class HgGlobalConfigInIndexResource extends JsonEnricherBase {
private final Provider<ScmPathInfoStore> scmPathInfoStore;
@Inject
public HgConfigInIndexResource(Provider<ScmPathInfoStore> scmPathInfoStore, ObjectMapper objectMapper) {
public HgGlobalConfigInIndexResource(Provider<ScmPathInfoStore> scmPathInfoStore, ObjectMapper objectMapper) {
super(objectMapper);
this.scmPathInfoStore = scmPathInfoStore;
}
@Override
public void enrich(JsonEnricherContext context) {
if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.read(HgConfig.PERMISSION).isPermitted()) {
if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.read(HgGlobalConfig.PERMISSION).isPermitted()) {
String hgConfigUrl = new LinkBuilder(scmPathInfoStore.get().get(), HgConfigResource.class)
.method("get")
.parameters()

View File

@@ -21,15 +21,16 @@
* 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.annotations.VisibleForTesting;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import javax.inject.Inject;
@@ -39,27 +40,23 @@ import static de.otto.edison.hal.Links.linkingTo;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@Mapper
public abstract class HgConfigToHgConfigDtoMapper extends BaseMapper<HgConfig, HgConfigDto> {
public abstract class HgGlobalConfigToHgGlobalConfigDtoMapper extends BaseMapper<HgGlobalConfig, HgGlobalGlobalConfigDto> {
@Inject
private ScmPathInfoStore scmPathInfoStore;
private HgConfigLinks links;
@VisibleForTesting
void setLinks(HgConfigLinks links) {
this.links = links;
}
@AfterMapping
void appendLinks(HgConfig config, @MappingTarget HgConfigDto target) {
Links.Builder linksBuilder = linkingTo().self(self());
void appendLinks(HgGlobalConfig config, @MappingTarget HgGlobalGlobalConfigDto target) {
HgConfigLinks.ConfigLinks configLinks = links.global();
Links.Builder linksBuilder = linkingTo().self(configLinks.get());
if (ConfigurationPermissions.write(config).isPermitted()) {
linksBuilder.single(link("update", update()));
linksBuilder.single(link("update", configLinks.update()));
}
target.add(linksBuilder.build());
}
private String self() {
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), HgConfigResource.class);
return linkBuilder.method("get").parameters().href();
}
private String update() {
LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get(), HgConfigResource.class);
return linkBuilder.method("update").parameters().href();
}
}

View File

@@ -34,12 +34,13 @@ import lombok.Setter;
@Setter
@NoArgsConstructor
@SuppressWarnings("java:S2160") // we don't need equals for dto
public class HgConfigDto extends HalRepresentation implements UpdateHgConfigDto {
public class HgGlobalGlobalConfigDto extends HalRepresentation implements UpdateHgGlobalConfigDto {
private boolean disabled;
@Encoding
private String encoding;
private String hgBinary;
private boolean showRevisionInId;
private boolean enableHttpPostArgs;

View File

@@ -0,0 +1,45 @@
/*
* 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 de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@SuppressWarnings("java:S2160") // we don't need equals for a dto
public class HgRepositoryConfigDto extends HalRepresentation {
@Encoding
private String encoding;
public HgRepositoryConfigDto(Links links) {
super(links);
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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 sonia.scm.plugin.Extension;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import javax.inject.Inject;
@Extension
@Enrich(Repository.class)
public class HgRepositoryConfigEnricher implements HalEnricher {
private final HgConfigLinks links;
@Inject
public HgRepositoryConfigEnricher(HgConfigLinks links) {
this.links = links;
}
@Override
public void enrich(HalEnricherContext context, HalAppender appender) {
Repository repository = context.oneRequireByType(Repository.class);
if (isHgRepository(repository)) {
appender.appendLink("configuration", links.repository(repository).get());
}
}
private boolean isHgRepository(Repository repository) {
return HgRepositoryHandler.TYPE_NAME.equals(repository.getType());
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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 com.google.common.annotations.VisibleForTesting;
import de.otto.edison.hal.Links;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ObjectFactory;
import sonia.scm.repository.HgRepositoryConfig;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import javax.inject.Inject;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
@Mapper
public abstract class HgRepositoryConfigMapper {
@Inject
private HgConfigLinks links;
@VisibleForTesting
void setLinks(HgConfigLinks links) {
this.links = links;
}
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
abstract HgRepositoryConfigDto map(@Context Repository repository, HgRepositoryConfig config);
abstract HgRepositoryConfig map(HgRepositoryConfigDto dto);
@ObjectFactory
HgRepositoryConfigDto createDto(@Context Repository repository) {
HgConfigLinks.ConfigLinks configLinks = this.links.repository(repository);
Links.Builder linksBuilder = linkingTo().self(configLinks.get());
if (RepositoryPermissions.custom("hg", repository).isPermitted()) {
linksBuilder.single(link("update", configLinks.update()));
}
return new HgRepositoryConfigDto(linksBuilder.build());
}
}

View File

@@ -0,0 +1,149 @@
/*
* 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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import sonia.scm.repository.HgRepositoryConfigStore;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.HgVndMediaType;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
public class HgRepositoryConfigResource {
private final RepositoryManager repositoryManager;
private final HgRepositoryConfigStore store;
private final HgRepositoryConfigMapper mapper;
@Inject
public HgRepositoryConfigResource(RepositoryManager repositoryManager, HgRepositoryConfigStore store, HgRepositoryConfigMapper mapper) {
this.repositoryManager = repositoryManager;
this.store = store;
this.mapper = mapper;
}
@GET
@Path("")
@Produces(HgVndMediaType.REPO_CONFIG)
@Operation(
summary = "Hg configuration",
description = "Returns the global mercurial configuration.",
tags = "Mercurial",
operationId = "hg_get_repo_config"
)
@ApiResponse(
responseCode = "200",
description = "success",
content = @Content(
mediaType = HgVndMediaType.REPO_CONFIG,
schema = @Schema(implementation = HgGlobalGlobalConfigDto.class)
)
)
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository:read:{repositoryId}\" privilege")
@ApiResponse(
responseCode = "500",
description = "internal server error",
content = @Content(
mediaType = VndMediaType.ERROR_TYPE,
schema = @Schema(implementation = ErrorDto.class)
))
public HgRepositoryConfigDto getHgRepositoryConfig(@PathParam("namespace") String namespace, @PathParam("name") String name) {
Repository repository = getRepository(namespace, name);
return mapper.map(repository, store.of(repository));
}
@PUT
@Path("")
@Consumes(HgVndMediaType.REPO_CONFIG)
@Operation(
summary = "Modify hg configuration",
description = "Modifies the repository specific mercurial configuration.",
tags = "Mercurial",
operationId = "hg_put_repo_config",
requestBody = @RequestBody(
content = @Content(
mediaType = HgVndMediaType.CONFIG,
schema = @Schema(implementation = UpdateHgGlobalConfigDto.class),
examples = @ExampleObject(
name = "Overwrites current configuration with this one.",
value = "{\n \"encoding\":\"UTF-8\" \n}",
summary = "Simple update configuration"
)
)
)
)
@ApiResponse(
responseCode = "204",
description = "update success"
)
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository:hg:{repositoryId}\" privilege")
@ApiResponse(
responseCode = "500",
description = "internal server error",
content = @Content(
mediaType = VndMediaType.ERROR_TYPE,
schema = @Schema(implementation = ErrorDto.class)
))
public Response updateHgRepositoryConfig(
@PathParam("namespace") String namespace,
@PathParam("name") String name,
@Valid HgRepositoryConfigDto dto
) {
Repository repository = getRepository(namespace, name);
store.store(repository, mapper.map(dto));
return Response.noContent().build();
}
private Repository getRepository(String namespace, String name) {
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
Repository repository = repositoryManager.get(namespaceAndName);
if (repository == null) {
throw notFound(entity(namespaceAndName));
}
return repository;
}
}

View File

@@ -24,7 +24,7 @@
package sonia.scm.api.v2.resources;
interface UpdateHgConfigDto {
interface UpdateHgGlobalConfigDto {
boolean isDisabled();
String getHgBinary();

View File

@@ -24,10 +24,10 @@
package sonia.scm.autoconfig;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
public interface AutoConfigurator {
void configure(HgConfig config);
void configure(HgGlobalConfig config);
}

View File

@@ -26,7 +26,7 @@ package sonia.scm.autoconfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
public class NoOpAutoConfigurator implements AutoConfigurator {
@@ -36,7 +36,7 @@ public class NoOpAutoConfigurator implements AutoConfigurator {
}
@Override
public void configure(HgConfig config) {
public void configure(HgGlobalConfig config) {
// if we do not know the environment, we could not configure mercurial
LOG.debug("no mercurial autoconfiguration available on this platform");
}

View File

@@ -29,7 +29,7 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgVerifier;
import java.io.File;
@@ -68,7 +68,7 @@ public class PosixAutoConfigurator implements AutoConfigurator {
}
@Override
public void configure(HgConfig config) {
public void configure(HgGlobalConfig config) {
Optional<Path> hg = findInPath();
if (hg.isPresent()) {
config.setHgBinary(hg.get().toAbsolutePath().toString());

View File

@@ -28,7 +28,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgVerifier;
import java.io.File;
@@ -72,7 +72,7 @@ public class WindowsAutoConfigurator implements AutoConfigurator {
}
@Override
public void configure(HgConfig config) {
public void configure(HgGlobalConfig config) {
Set<String> fsPaths = new LinkedHashSet<>(pathFromEnv());
resolveRegistryKeys(fsPaths);

View File

@@ -60,7 +60,7 @@ public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder {
static final String ENV_TRANSACTION_ID = "SCM_TRANSACTION_ID";
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
private final HgRepositoryHandler repositoryHandler;
private final HgConfigResolver configResolver;
private final HookEnvironment hookEnvironment;
private final HookServer server;
@@ -68,11 +68,11 @@ public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder {
@Inject
public DefaultHgEnvironmentBuilder(
AccessTokenBuilderFactory accessTokenBuilderFactory, HgRepositoryHandler repositoryHandler,
AccessTokenBuilderFactory accessTokenBuilderFactory, HgConfigResolver configResolver,
HookEnvironment hookEnvironment, HookServer server
) {
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
this.repositoryHandler = repositoryHandler;
this.configResolver = configResolver;
this.hookEnvironment = hookEnvironment;
this.server = server;
}
@@ -94,9 +94,8 @@ public class DefaultHgEnvironmentBuilder implements HgEnvironmentBuilder {
}
private void read(ImmutableMap.Builder<String, String> env, Repository repository) {
HgConfig config = repositoryHandler.getConfig();
File directory = repositoryHandler.getDirectory(repository.getId());
HgConfig config = configResolver.resolve(repository);
File directory = config.getDirectory();
env.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
env.put(ENV_REPOSITORY_ID, repository.getId());

View File

@@ -24,128 +24,17 @@
package sonia.scm.repository;
import lombok.Value;
import sonia.scm.util.Util;
import java.io.File;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
@Value
public class HgConfig {
/**
*
* @author Sebastian Sdorra
*/
@XmlRootElement(name = "config")
public class HgConfig extends RepositoryConfig {
public static final String PERMISSION = "hg";
@Override
@XmlTransient // Only for permission checks, don't serialize to XML
public String getId() {
// Don't change this without migrating SCM permission configuration!
return PERMISSION;
}
/**
* Method description
*
*
* @return
*/
public String getEncoding()
{
return encoding;
}
/**
* Method description
*
*
* @return
*/
public String getHgBinary()
{
return hgBinary;
}
/**
* Method description
*
*
* @return
*/
public boolean isShowRevisionInId()
{
return showRevisionInId;
}
public boolean isEnableHttpPostArgs() {
return enableHttpPostArgs;
}
/**
* Method description
*
*
* @return
*/
@Override
public boolean isValid()
{
return super.isValid() && Util.isNotEmpty(hgBinary);
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param encoding
*/
public void setEncoding(String encoding)
{
this.encoding = encoding;
}
/**
* Method description
*
*
* @param hgBinary
*/
public void setHgBinary(String hgBinary)
{
this.hgBinary = hgBinary;
}
/**
* Method description
*
*
* @param showRevisionInId
*/
public void setShowRevisionInId(boolean showRevisionInId)
{
this.showRevisionInId = showRevisionInId;
}
public void setEnableHttpPostArgs(boolean enableHttpPostArgs) {
this.enableHttpPostArgs = enableHttpPostArgs;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String encoding = "UTF-8";
/** Field description */
private String hgBinary;
/** Field description */
private boolean showRevisionInId = false;
private boolean enableHttpPostArgs = false;
String hgBinary;
String encoding;
boolean showRevisionInId;
boolean enableHttpPostArgs;
File directory;
}

View File

@@ -0,0 +1,86 @@
/*
* 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 com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import javax.inject.Inject;
import java.io.File;
import java.util.function.Function;
public class HgConfigResolver {
private final HgRepositoryHandler repositoryHandler;
private final Function<Repository, HgRepositoryConfig> repositoryConfigResolver;
private final Function<Repository, File> directoryResolver;
@Inject
public HgConfigResolver(HgRepositoryHandler repositoryHandler, HgRepositoryConfigStore repositoryConfigStore) {
this(
repositoryHandler,
repositoryConfigStore::of,
repository -> repositoryHandler.getDirectory(repository.getId())
);
}
@VisibleForTesting
public HgConfigResolver(HgRepositoryHandler repositoryHandler) {
this(
repositoryHandler,
repository -> repositoryHandler.getDirectory(repository.getId())
);
}
@VisibleForTesting
public HgConfigResolver(HgRepositoryHandler repositoryHandler, Function<Repository, File> directoryResolver) {
this.repositoryHandler = repositoryHandler;
this.repositoryConfigResolver = (repository -> new HgRepositoryConfig());
this.directoryResolver = directoryResolver;
}
@VisibleForTesting
public HgConfigResolver(HgRepositoryHandler repositoryHandler, Function<Repository, HgRepositoryConfig> repositoryConfigResolver, Function<Repository, File> directoryResolver) {
this.repositoryHandler = repositoryHandler;
this.repositoryConfigResolver = repositoryConfigResolver;
this.directoryResolver = directoryResolver;
}
public boolean isConfigured() {
return repositoryHandler.isConfigured();
}
public HgConfig resolve(Repository repository) {
HgGlobalConfig globalConfig = repositoryHandler.getConfig();
HgRepositoryConfig repositoryConfig = repositoryConfigResolver.apply(repository);
return new HgConfig(
globalConfig.getHgBinary(),
MoreObjects.firstNonNull(repositoryConfig.getEncoding(), globalConfig.getEncoding()),
globalConfig.isShowRevisionInId(),
globalConfig.isEnableHttpPostArgs(),
directoryResolver.apply(repository)
);
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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.util.Util;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
*
* @author Sebastian Sdorra
*/
@XmlRootElement(name = "config")
public class HgGlobalConfig extends RepositoryConfig {
public static final String PERMISSION = "hg";
@Override
@XmlTransient // Only for permission checks, don't serialize to XML
public String getId() {
// Don't change this without migrating SCM permission configuration!
return PERMISSION;
}
/**
* Method description
*
*
* @return
*/
public String getEncoding()
{
return encoding;
}
/**
* Method description
*
*
* @return
*/
public String getHgBinary()
{
return hgBinary;
}
/**
* Method description
*
*
* @return
*/
public boolean isShowRevisionInId()
{
return showRevisionInId;
}
public boolean isEnableHttpPostArgs() {
return enableHttpPostArgs;
}
/**
* Method description
*
*
* @return
*/
@Override
public boolean isValid()
{
return super.isValid() && Util.isNotEmpty(hgBinary);
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param encoding
*/
public void setEncoding(String encoding)
{
this.encoding = encoding;
}
/**
* Method description
*
*
* @param hgBinary
*/
public void setHgBinary(String hgBinary)
{
this.hgBinary = hgBinary;
}
/**
* Method description
*
*
* @param showRevisionInId
*/
public void setShowRevisionInId(boolean showRevisionInId)
{
this.showRevisionInId = showRevisionInId;
}
public void setEnableHttpPostArgs(boolean enableHttpPostArgs) {
this.enableHttpPostArgs = enableHttpPostArgs;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String encoding = "UTF-8";
/** Field description */
private String hgBinary;
/** Field description */
private boolean showRevisionInId = false;
private boolean enableHttpPostArgs = false;
}

View File

@@ -0,0 +1,35 @@
/*
* 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 lombok.Data;
import javax.xml.bind.annotation.XmlRootElement;
@Data
@XmlRootElement
public class HgRepositoryConfig {
String encoding;
}

View File

@@ -0,0 +1,59 @@
/*
* 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.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory;
import javax.inject.Inject;
public class HgRepositoryConfigStore {
private static final String STORE_NAME = "hgConfig";
private final ConfigurationStoreFactory factory;
@Inject
public HgRepositoryConfigStore(ConfigurationStoreFactory factory) {
this.factory = factory;
}
public HgRepositoryConfig of(Repository repository) {
ConfigurationStore<HgRepositoryConfig> store = store(repository);
return store.getOptional().orElse(new HgRepositoryConfig());
}
public void store(Repository repository, HgRepositoryConfig config) {
RepositoryPermissions.custom("hg", repository).check();
store(repository).set(config);
}
private ConfigurationStore<HgRepositoryConfig> store(Repository repository) {
return factory.withType(HgRepositoryConfig.class)
.withName(STORE_NAME)
.forRepository(repository)
.build();
}
}

View File

@@ -25,7 +25,6 @@
package sonia.scm.repository;
import com.aragost.javahg.RepositoryConfiguration;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.hooks.HookEnvironment;
@@ -38,32 +37,21 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Map;
import java.util.function.Function;
@Singleton
public class HgRepositoryFactory {
private static final Logger LOG = LoggerFactory.getLogger(HgRepositoryFactory.class);
private final HgRepositoryHandler handler;
private final HgConfigResolver configResolver;
private final HookEnvironment hookEnvironment;
private final HgEnvironmentBuilder environmentBuilder;
private final Function<Repository, File> directoryResolver;
@Inject
public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder) {
this(
handler, hookEnvironment, environmentBuilder,
repository -> handler.getDirectory(repository.getId())
);
}
@VisibleForTesting
public HgRepositoryFactory(HgRepositoryHandler handler, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder, Function<Repository, File> directoryResolver) {
this.handler = handler;
public HgRepositoryFactory(HgConfigResolver configResolver, HookEnvironment hookEnvironment, HgEnvironmentBuilder environmentBuilder) {
this.configResolver = configResolver;
this.hookEnvironment = hookEnvironment;
this.environmentBuilder = environmentBuilder;
this.directoryResolver = directoryResolver;
}
public com.aragost.javahg.Repository openForRead(Repository repository) {
@@ -75,7 +63,8 @@ public class HgRepositoryFactory {
}
private com.aragost.javahg.Repository open(Repository repository, Map<String, String> environment) {
File directory = directoryResolver.apply(repository);
HgConfig config = configResolver.resolve(repository);
File directory = config.getDirectory();
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
repoConfiguration.getEnvironment().putAll(environment);
@@ -84,18 +73,18 @@ public class HgRepositoryFactory {
boolean pending = hookEnvironment.isPending();
repoConfiguration.setEnablePendingChangesets(pending);
Charset encoding = encoding();
Charset encoding = encoding(config);
repoConfiguration.setEncoding(encoding);
repoConfiguration.setHgBin(handler.getConfig().getHgBinary());
repoConfiguration.setHgBin(config.getHgBinary());
LOG.trace("open hg repository {}: encoding: {}, pending: {}", directory, encoding, pending);
return com.aragost.javahg.Repository.open(repoConfiguration, directory);
}
private Charset encoding() {
String charset = handler.getConfig().getEncoding();
private Charset encoding(HgConfig config) {
String charset = config.getEncoding();
try {
return Charset.forName(charset);
} catch (UnsupportedCharsetException ex) {

View File

@@ -55,7 +55,7 @@ import java.io.OutputStream;
@Singleton
@Extension
public class HgRepositoryHandler
extends AbstractSimpleRepositoryHandler<HgConfig> {
extends AbstractSimpleRepositoryHandler<HgGlobalConfig> {
public static final String TYPE_DISPLAYNAME = "Mercurial";
public static final String TYPE_NAME = "hg";
@@ -84,7 +84,7 @@ public class HgRepositoryHandler
this.configurator = configurator;
}
public void doAutoConfiguration(HgConfig autoConfig) {
public void doAutoConfiguration(HgGlobalConfig autoConfig) {
configurator.configure(autoConfig);
}
@@ -99,7 +99,7 @@ public class HgRepositoryHandler
super.loadConfig();
if (config == null) {
config = new HgConfig();
config = new HgGlobalConfig();
storeConfig();
}
@@ -109,7 +109,7 @@ public class HgRepositoryHandler
}
}
private boolean isConfigValid(HgConfig config) {
private boolean isConfigValid(HgGlobalConfig config) {
return config.isValid() && new HgVerifier().isValid(config);
}
@@ -175,8 +175,8 @@ public class HgRepositoryHandler
}
@Override
protected Class<HgConfig> getConfigClass() {
return HgConfig.class;
protected Class<HgGlobalConfig> getConfigClass() {
return HgGlobalConfig.class;
}
private void writeHgExtensions(SCMContextProvider context) {

View File

@@ -47,7 +47,7 @@ public class HgVerifier {
this.versionResolver = versionResolver;
}
public boolean isValid(HgConfig config) {
public boolean isValid(HgGlobalConfig config) {
return isValid(config.getHgBinary());
}

View File

@@ -25,8 +25,8 @@
package sonia.scm.repository.hooks;
import sonia.scm.NotFoundException;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.spi.HgHookContextProvider;
@@ -36,13 +36,13 @@ import javax.inject.Inject;
public class HookContextProviderFactory {
private final RepositoryManager repositoryManager;
private final HgRepositoryHandler repositoryHandler;
private final HgConfigResolver configResolver;
private final HgRepositoryFactory repositoryFactory;
@Inject
public HookContextProviderFactory(RepositoryManager repositoryManager, HgRepositoryHandler repositoryHandler, HgRepositoryFactory repositoryFactory) {
public HookContextProviderFactory(RepositoryManager repositoryManager, HgConfigResolver configResolver, HgRepositoryFactory repositoryFactory) {
this.repositoryManager = repositoryManager;
this.repositoryHandler = repositoryHandler;
this.configResolver = configResolver;
this.repositoryFactory = repositoryFactory;
}
@@ -51,7 +51,7 @@ public class HookContextProviderFactory {
if (repository == null) {
throw new NotFoundException(Repository.class, repositoryId);
}
return new HgHookContextProvider(repositoryHandler, repositoryFactory, repository, node);
return new HgHookContextProvider(configResolver, repositoryFactory, repository, node);
}
}

View File

@@ -28,8 +28,8 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Repository;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.RepositoryProvider;
import java.io.Closeable;
@@ -43,14 +43,14 @@ import java.io.File;
*/
public class HgCommandContext implements Closeable, RepositoryProvider {
private final HgRepositoryHandler handler;
private final HgConfigResolver configResolver;
private final HgRepositoryFactory factory;
private final sonia.scm.repository.Repository scmRepository;
private Repository repository;
public HgCommandContext(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository) {
this.handler = handler;
public HgCommandContext(HgConfigResolver configResolver, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository) {
this.configResolver = configResolver;
this.factory = factory;
this.scmRepository = scmRepository;
}
@@ -66,14 +66,17 @@ public class HgCommandContext implements Closeable, RepositoryProvider {
return factory.openForWrite(scmRepository);
}
private HgConfig config;
public HgConfig getConfig()
{
return handler.getConfig();
public HgConfig getConfig() {
if (config == null) {
config = configResolver.resolve(scmRepository);
}
return config;
}
public File getDirectory() {
return handler.getDirectory(scmRepository.getId());
return getConfig().getDirectory();
}
public sonia.scm.repository.Repository getScmRepository() {
@@ -85,7 +88,6 @@ public class HgCommandContext implements Closeable, RepositoryProvider {
return getScmRepository();
}
@Override
public void close() {
if (repository != null) {

View File

@@ -27,8 +27,9 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
import sonia.scm.web.HgUtil;
@@ -40,15 +41,15 @@ public class HgHookChangesetProvider implements HookChangesetProvider {
private static final Logger LOG = LoggerFactory.getLogger(HgHookChangesetProvider.class);
private final HgRepositoryHandler handler;
private final HgConfigResolver configResolver;
private final HgRepositoryFactory factory;
private final sonia.scm.repository.Repository scmRepository;
private final String startRev;
private HookChangesetResponse response;
public HgHookChangesetProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository, String startRev) {
this.handler = handler;
public HgHookChangesetProvider(HgConfigResolver configResolver, HgRepositoryFactory factory, sonia.scm.repository.Repository scmRepository, String startRev) {
this.configResolver = configResolver;
this.factory = factory;
this.scmRepository = scmRepository;
this.startRev = startRev;
@@ -62,7 +63,8 @@ public class HgHookChangesetProvider implements HookChangesetProvider {
try {
repository = factory.openForRead(scmRepository);
HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, handler.getConfig());
HgConfig config = configResolver.resolve(scmRepository);
HgLogChangesetCommand cmd = HgLogChangesetCommand.on(repository, config);
response = new HookChangesetResponse(
cmd.rev(startRev.concat(":").concat(HgUtil.REVISION_TIP)).execute()

View File

@@ -26,8 +26,8 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.HgHookBranchProvider;
import sonia.scm.repository.api.HgHookMessageProvider;
@@ -61,8 +61,8 @@ public class HgHookContextProvider extends HookContextProvider {
private HgHookBranchProvider hookBranchProvider;
private HgHookTagProvider hookTagProvider;
public HgHookContextProvider(HgRepositoryHandler handler, HgRepositoryFactory factory, Repository repository, String startRev) {
this.hookChangesetProvider = new HgHookChangesetProvider(handler, factory, repository, startRev);
public HgHookContextProvider(HgConfigResolver configResolver, HgRepositoryFactory factory, Repository repository, String startRev) {
this.hookChangesetProvider = new HgHookChangesetProvider(configResolver, factory, repository, startRev);
}
@Override

View File

@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
import com.google.common.io.Closeables;
import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Feature;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -70,6 +71,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
private final ScmEventBus eventBus;
HgRepositoryServiceProvider(HgRepositoryHandler handler,
HgConfigResolver configResolver,
HgRepositoryFactory factory,
HgRepositoryHookEventFactory eventFactory,
ScmEventBus eventBus,
@@ -77,7 +79,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider {
this.handler = handler;
this.eventBus = eventBus;
this.eventFactory = eventFactory;
this.context = new HgCommandContext(handler, factory, repository);
this.context = new HgCommandContext(configResolver, factory, repository);
this.lazyChangesetResolver = new HgLazyChangesetResolver(factory, repository);
}

View File

@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
import com.google.inject.Inject;
import sonia.scm.event.ScmEventBus;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository;
@@ -38,17 +39,20 @@ import sonia.scm.repository.Repository;
public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
private final HgRepositoryHandler handler;
private final HgConfigResolver configResolver;
private final HgRepositoryFactory factory;
private final ScmEventBus eventBus;
private final HgRepositoryHookEventFactory eventFactory;
@Inject
public HgRepositoryServiceResolver(HgRepositoryHandler handler,
HgConfigResolver configResolver,
HgRepositoryFactory factory,
ScmEventBus eventBus,
HgRepositoryHookEventFactory eventFactory
) {
this.handler = handler;
this.configResolver = configResolver;
this.factory = factory;
this.eventBus = eventBus;
this.eventFactory = eventFactory;
@@ -59,7 +63,7 @@ public class HgRepositoryServiceResolver implements RepositoryServiceResolver {
HgRepositoryServiceProvider provider = null;
if (HgRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
provider = new HgRepositoryServiceProvider(handler, factory, eventFactory, eventBus, repository);
provider = new HgRepositoryServiceProvider(handler, configResolver, factory, eventFactory, eventBus, repository);
}
return provider;

View File

@@ -28,7 +28,7 @@ import com.google.common.io.ByteStreams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgExtensions;
import java.io.IOException;
@@ -41,15 +41,15 @@ public class HgVersionCommand {
private static final Logger LOG = LoggerFactory.getLogger(HgVersionCommand.class);
public static final String UNKNOWN = "python/x.y.z mercurial/x.y.z";
private final HgConfig config;
private final HgGlobalConfig config;
private final String extension;
private final ProcessExecutor executor;
public HgVersionCommand(HgConfig config) {
public HgVersionCommand(HgGlobalConfig config) {
this(config, extension(), command -> new ProcessBuilder(command).start());
}
HgVersionCommand(HgConfig config, String extension, ProcessExecutor executor) {
HgVersionCommand(HgGlobalConfig config, String extension, ProcessExecutor executor) {
this.config = config;
this.extension = extension;
this.executor = executor;

View File

@@ -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.spi.javahg;
//~--- non-JDK imports --------------------------------------------------------
@@ -61,9 +61,7 @@ public class HgIncomingChangesetCommand
*
* @return
*/
public static HgIncomingChangesetCommand on(Repository repository,
HgConfig config)
{
public static HgIncomingChangesetCommand on(Repository repository, HgConfig config) {
return new HgIncomingChangesetCommand(repository, config);
}

View File

@@ -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.spi.javahg;
//~--- non-JDK imports --------------------------------------------------------
@@ -52,9 +52,7 @@ public abstract class HgIncomingOutgoingChangesetCommand
* @param repository
* @param config
*/
public HgIncomingOutgoingChangesetCommand(Repository repository,
HgConfig config)
{
public HgIncomingOutgoingChangesetCommand(Repository repository, HgConfig config) {
super(repository, config);
}

View File

@@ -21,13 +21,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.spi.javahg;
//~--- non-JDK imports --------------------------------------------------------
import com.aragost.javahg.Repository;
import sonia.scm.repository.HgConfig;
/**

View File

@@ -31,10 +31,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgEnvironmentBuilder;
import sonia.scm.repository.HgExtensions;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryRequestListenerUtil;
import sonia.scm.repository.spi.ScmProviderHttpServlet;
@@ -60,38 +60,41 @@ import java.util.List;
@Singleton
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet {
/** Field description */
private static final long serialVersionUID = -3492811300905099810L;
/** the logger for HgCGIServlet */
private static final Logger logger =
LoggerFactory.getLogger(HgCGIServlet.class);
//~--- constructors ---------------------------------------------------------
private final CGIExecutorFactory cgiExecutorFactory;
private final HgConfigResolver configResolver;
private final File extension;
private final ScmConfiguration configuration;
private final HgCGIExceptionHandler exceptionHandler;
private final RepositoryRequestListenerUtil requestListenerUtil;
private final HgEnvironmentBuilder environmentBuilder;
@Inject
public HgCGIServlet(CGIExecutorFactory cgiExecutorFactory,
HgConfigResolver configResolver,
ScmConfiguration configuration,
HgRepositoryHandler handler,
RepositoryRequestListenerUtil requestListenerUtil,
HgEnvironmentBuilder environmentBuilder)
{
this.cgiExecutorFactory = cgiExecutorFactory;
this.configResolver = configResolver;
this.configuration = configuration;
this.handler = handler;
this.requestListenerUtil = requestListenerUtil;
this.environmentBuilder = environmentBuilder;
this.exceptionHandler = new HgCGIExceptionHandler();
this.extension = HgExtensions.CGISERVE.getFile(SCMContext.getContext());
}
//~--- methods --------------------------------------------------------------
@Override
public void service(HttpServletRequest request,
HttpServletResponse response, Repository repository)
{
if (!handler.isConfigured())
if (!configResolver.isConfigured())
{
exceptionHandler.sendFormattedError(request, response,
HgCGIExceptionHandler.ERROR_NOT_CONFIGURED);
@@ -141,10 +144,9 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
EnvList env = executor.getEnvironment();
environmentBuilder.write(repository).forEach(env::set);
File directory = handler.getDirectory(repository.getId());
executor.setWorkDirectory(directory);
HgConfig config = configResolver.resolve(repository);
executor.setWorkDirectory(config.getDirectory());
HgConfig config = handler.getConfig();
executor.setArgs(createArgs(config));
executor.execute(config.getHgBinary());
}
@@ -174,26 +176,4 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
args.add("--config");
args.add(key + "=" + value);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final CGIExecutorFactory cgiExecutorFactory;
/** Field description */
private final File extension;
/** Field description */
private final ScmConfiguration configuration;
/** Field description */
private final HgCGIExceptionHandler exceptionHandler;
/** Field description */
private final HgRepositoryHandler handler;
/** Field description */
private final RepositoryRequestListenerUtil requestListenerUtil;
private final HgEnvironmentBuilder environmentBuilder;
}

View File

@@ -28,8 +28,9 @@ package sonia.scm.web;
import com.google.inject.servlet.ServletModule;
import org.mapstruct.factory.Mappers;
import sonia.scm.api.v2.resources.HgConfigDtoToHgConfigMapper;
import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper;
import sonia.scm.api.v2.resources.HgGlobalConfigDtoToHgConfigMapper;
import sonia.scm.api.v2.resources.HgGlobalConfigToHgGlobalConfigDtoMapper;
import sonia.scm.api.v2.resources.HgRepositoryConfigMapper;
import sonia.scm.plugin.Extension;
import sonia.scm.repository.spi.HgWorkingCopyFactory;
import sonia.scm.repository.spi.SimpleHgWorkingCopyFactory;
@@ -43,8 +44,9 @@ public class HgServletModule extends ServletModule {
@Override
protected void configureServlets() {
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
bind(HgConfigToHgConfigDtoMapper.class).to(Mappers.getMapper(HgConfigToHgConfigDtoMapper.class).getClass());
bind(HgGlobalConfigDtoToHgConfigMapper.class).to(Mappers.getMapperClass(HgGlobalConfigDtoToHgConfigMapper.class));
bind(HgGlobalConfigToHgGlobalConfigDtoMapper.class).to(Mappers.getMapperClass(HgGlobalConfigToHgGlobalConfigDtoMapper.class));
bind(HgRepositoryConfigMapper.class).to(Mappers.getMapperClass(HgRepositoryConfigMapper.class));
bind(HgWorkingCopyFactory.class).to(SimpleHgWorkingCopyFactory.class);
}

View File

@@ -21,13 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.web;
public class HgVndMediaType {
private static final String PREFIX = VndMediaType.PREFIX + "hgConfig";
public static final String REPO_CONFIG = PREFIX + "-repo" + VndMediaType.SUFFIX;
public static final String CONFIG = PREFIX + VndMediaType.SUFFIX;
public static final String PACKAGES = PREFIX + "-packages" + VndMediaType.SUFFIX;
public static final String INSTALLATIONS = PREFIX + "-installation" + VndMediaType.SUFFIX;

View File

@@ -0,0 +1,85 @@
/*
* 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 { Repository } from "@scm-manager/ui-types";
import { ErrorNotification, InputField, Level, Loading, SubmitButton, Notification } from "@scm-manager/ui-components";
import React, { FC, FormEvent, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { HgRepositoryConfiguration, useHgRepositoryConfiguration, useUpdateHgRepositoryConfiguration } from "./hooks";
type Props = {
repository: Repository;
};
const HgRepositoryConfigurationForm: FC<Props> = ({ repository }) => {
const [t] = useTranslation("plugins");
const { isLoading, error, data } = useHgRepositoryConfiguration(repository);
const { isLoading: isUpdating, error: updateError, update, updated } = useUpdateHgRepositoryConfiguration();
const [configuration, setConfiguration] = useState<HgRepositoryConfiguration | null>(null);
useEffect(() => {
setConfiguration(data);
}, [data]);
if (error) {
return <ErrorNotification error={error} />;
}
if (isLoading || !configuration) {
return <Loading />;
}
const encodingChanged = (value: string) => {
const encoding = value ? value : undefined;
setConfiguration({
...configuration,
encoding
});
};
const submit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
update(configuration);
};
const readOnly = !configuration._links.update;
return (
<form onSubmit={submit}>
<ErrorNotification error={updateError} />
{updated ? <Notification type="info">{t("scm-hg-plugin.config.success")}</Notification> : null}
<InputField
name="encoding"
label={t("scm-hg-plugin.config.encoding")}
helpText={t("scm-hg-plugin.config.encodingHelpText")}
value={configuration.encoding || ""}
onChange={encodingChanged}
disabled={readOnly}
/>
{!readOnly ? (
<Level right={<SubmitButton loading={isUpdating} label={t("scm-hg-plugin.config.submit")} />} />
) : null}
</form>
);
};
export default HgRepositoryConfigurationForm;

View File

@@ -0,0 +1,86 @@
/*
* 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 { useEffect, useState } from "react";
import { apiClient } from "@scm-manager/ui-components";
import { HalRepresentation, Link, Repository } from "@scm-manager/ui-types";
export type HgRepositoryConfiguration = HalRepresentation & {
encoding?: string;
};
export const useHgRepositoryConfiguration = (repository: Repository) => {
const [isLoading, setLoading] = useState(false);
const [data, setData] = useState<HgRepositoryConfiguration | null>(null);
const [error, setError] = useState<Error | null>(null);
const link = (repository._links.configuration as Link)?.href;
useEffect(() => {
setError(null);
if (link) {
setLoading(true);
apiClient
.get(link)
.then(response => response.json())
.then((config: HgRepositoryConfiguration) => {
setData(config);
})
.catch(e => setError(e))
.finally(() => setLoading(false));
}
}, [link]);
return {
isLoading,
error,
data
};
};
export const useUpdateHgRepositoryConfiguration = () => {
const [isLoading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [updated, setUpdated] = useState(false);
const update = (configuration: HgRepositoryConfiguration) => {
if (!configuration._links.update) {
throw new Error("no update link on configuration");
}
const link = (configuration._links.update as Link).href;
setLoading(true);
setUpdated(false);
setError(null);
apiClient
.put(link, configuration, "application/vnd.scmm-hgConfig-repo+json;v=2")
.then(() => setUpdated(true))
.catch(e => setError(e))
.finally(() => setLoading(false));
};
return {
isLoading,
error,
update,
updated
};
};

View File

@@ -29,6 +29,7 @@ import { ConfigurationBinder as cfgBinder } from "@scm-manager/ui-components";
import HgGlobalConfiguration from "./HgGlobalConfiguration";
import HgBranchInformation from "./HgBranchInformation";
import HgTagInformation from "./HgTagInformation";
import HgRepositoryConfigurationForm from "./HgRepositoryConfigurationForm";
const hgPredicate = (props: any) => {
return props.repository && props.repository.type === "hg";
@@ -39,6 +40,10 @@ binder.bind("repos.branch-details.information", HgBranchInformation, hgPredicate
binder.bind("repos.tag-details.information", HgTagInformation, hgPredicate);
binder.bind("repos.repository-avatar", HgAvatar, hgPredicate);
// bind repository specific configuration
binder.bind("repo-config.route", HgRepositoryConfigurationForm, hgPredicate);
// bind global configuration
cfgBinder.bindGlobal("/hg", "scm-hg-plugin.config.link", "hgConfig", HgGlobalConfiguration);

View File

@@ -21,7 +21,9 @@
"enableHttpPostArgsHelpText": "Aktiviert das experimentelle HttpPostArgs Protokoll von Mercurial. Das HttpPostArgs Protokoll verwendet den Post Request Body anstatt des HTTP Headers um Meta Informationen zu versenden. Dieses Vorgehen reduziert die Header Größe der Mercurial Requests. HttpPostArgs wird seit Mercurial 3.8 unterstützt.",
"disabled": "Deaktiviert",
"disabledHelpText": "Aktiviert oder deaktiviert das Mercurial Plugin.",
"required": "Dieser Konfigurationswert wird benötigt"
"required": "Dieser Konfigurationswert wird benötigt",
"submit": "Speichern",
"success": "Einstellungen wurden erfolgreich geändert"
}
},
"permissions" : {

View File

@@ -21,7 +21,9 @@
"enableHttpPostArgsHelpText": "Enables the experimental HttpPostArgs Protocol of mercurial. The HttpPostArgs Protocol uses the body of post requests to send the meta information instead of http headers. This helps to reduce the header size of mercurial requests. HttpPostArgs is supported since mercurial 3.8.",
"disabled": "Disabled",
"disabledHelpText": "Enable or disable the Mercurial plugin.",
"required": "This configuration value is required"
"required": "This configuration value is required",
"submit": "Submit",
"success": "Configuration changed successfully"
}
},
"permissions" : {

View File

@@ -34,11 +34,12 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.factory.Mappers;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;
import sonia.scm.web.RestDispatcher;
@@ -65,33 +66,41 @@ public class HgConfigResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
private RestDispatcher dispatcher = new RestDispatcher();
private final URI baseUri = URI.create("/");
private final RestDispatcher dispatcher = new RestDispatcher();
@InjectMocks
private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ScmPathInfoStore scmPathInfoStore;
@InjectMocks
private HgConfigToHgConfigDtoMapperImpl configToDtoMapper;
private HgGlobalConfigDtoToHgConfigMapperImpl dtoToConfigMapper;
@Mock
private HgRepositoryHandler repositoryHandler;
@Mock
private Provider<HgConfigAutoConfigurationResource> autoconfigResource;
private Provider<HgGlobalConfigAutoConfigurationResource> autoconfigResource;
@Mock
private Provider<HgRepositoryConfigResource> repositoryConfigResource;
@Before
public void prepareEnvironment() {
HgConfig gitConfig = createConfiguration();
HgGlobalConfig gitConfig = createConfiguration();
when(repositoryHandler.getConfig()).thenReturn(gitConfig);
HgConfigResource gitConfigResource =
new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, autoconfigResource);
HgConfigResource gitConfigResource = new HgConfigResource(
dtoToConfigMapper, createConfigToDtoMapper(), repositoryHandler,
autoconfigResource, repositoryConfigResource
);
dispatcher.addSingletonResource(gitConfigResource);
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
}
private HgGlobalConfigToHgGlobalConfigDtoMapper createConfigToDtoMapper() {
ScmPathInfoStore store = new ScmPathInfoStore();
store.set(() -> URI.create("/"));
HgConfigLinks links = new HgConfigLinks(store);
HgGlobalConfigToHgGlobalConfigDtoMapper mapper = Mappers.getMapper(
HgGlobalConfigToHgGlobalConfigDtoMapper.class
);
mapper.setLinks(links);
return mapper;
}
@Test
@@ -172,8 +181,8 @@ public class HgConfigResourceTest {
return response;
}
private HgConfig createConfiguration() {
HgConfig config = new HgConfig();
private HgGlobalConfig createConfiguration() {
HgGlobalConfig config = new HgGlobalConfig();
config.setDisabled(false);
return config;
}

View File

@@ -1,102 +0,0 @@
/*
* 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 org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig;
import java.net.URI;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsConfiguration;
import static sonia.scm.api.v2.resources.HgConfigTests.createConfiguration;
@RunWith(MockitoJUnitRunner.class)
public class HgConfigToHgConfigDtoMapperTest {
private URI baseUri = URI.create("http://example.com/base/");
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ScmPathInfoStore scmPathInfoStore;
@InjectMocks
private HgConfigToHgConfigDtoMapperImpl mapper;
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
private URI expectedBaseUri;
@Before
public void init() {
when(scmPathInfoStore.get().getApiRestUri()).thenReturn(baseUri);
expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2);
subjectThreadState.bind();
ThreadContext.bind(subject);
}
@After
public void unbindSubject() {
ThreadContext.unbindSubject();
}
@Test
public void shouldMapFields() {
HgConfig config = createConfiguration();
when(subject.isPermitted("configuration:write:hg")).thenReturn(true);
HgConfigDto dto = mapper.map(config);
assertEqualsConfiguration(dto);
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
}
@Test
public void shouldMapFieldsWithoutUpdate() {
HgConfig config = createConfiguration();
when(subject.isPermitted("configuration:write:hg")).thenReturn(false);
HgConfigDto dto = mapper.map(config);
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
assertFalse(dto.getLinks().hasLink("update"));
}
}

View File

@@ -36,7 +36,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.web.HgVndMediaType;
import sonia.scm.web.RestDispatcher;
@@ -56,31 +56,34 @@ import static org.mockito.Mockito.when;
password = "secret"
)
@RunWith(MockitoJUnitRunner.class)
public class HgConfigAutoConfigurationResourceTest {
public class HgGlobalConfigAutoConfigurationResourceTest {
@Rule
public ShiroRule shiro = new ShiroRule();
private RestDispatcher dispatcher = new RestDispatcher();
private final RestDispatcher dispatcher = new RestDispatcher();
@InjectMocks
private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper;
private HgGlobalConfigDtoToHgConfigMapperImpl dtoToConfigMapper;
@Mock
private HgRepositoryHandler repositoryHandler;
@Mock
private Provider<HgConfigAutoConfigurationResource> resourceProvider;
private Provider<HgGlobalConfigAutoConfigurationResource> resourceProvider;
@Mock
private Provider<HgRepositoryConfigResource> repositoryConfigResource;
@Before
public void prepareEnvironment() {
HgConfigAutoConfigurationResource resource =
new HgConfigAutoConfigurationResource(dtoToConfigMapper, repositoryHandler);
HgGlobalConfigAutoConfigurationResource resource = new HgGlobalConfigAutoConfigurationResource(dtoToConfigMapper, repositoryHandler);
when(resourceProvider.get()).thenReturn(resource);
dispatcher.addSingletonResource(
new HgConfigResource(null, null, null,
resourceProvider));
dispatcher.addSingletonResource(new HgConfigResource(
null, null, null,
resourceProvider, repositoryConfigResource
));
}
@Test
@@ -90,7 +93,7 @@ public class HgConfigAutoConfigurationResourceTest {
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
HgConfig actualConfig = captureConfig();
HgGlobalConfig actualConfig = captureConfig();
assertFalse(actualConfig.isDisabled());
}
@@ -110,7 +113,7 @@ public class HgConfigAutoConfigurationResourceTest {
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
HgConfig actualConfig = captureConfig();
HgGlobalConfig actualConfig = captureConfig();
assertTrue(actualConfig.isDisabled());
}
@@ -137,8 +140,8 @@ public class HgConfigAutoConfigurationResourceTest {
return response;
}
private HgConfig captureConfig() {
ArgumentCaptor<HgConfig> configCaptor = ArgumentCaptor.forClass(HgConfig.class);
private HgGlobalConfig captureConfig() {
ArgumentCaptor<HgGlobalConfig> configCaptor = ArgumentCaptor.forClass(HgGlobalConfig.class);
verify(repositoryHandler).doAutoConfiguration(configCaptor.capture());
return configCaptor.getValue();
}

View File

@@ -28,21 +28,21 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(MockitoJUnitRunner.class)
public class HgConfigDtoToHgConfigMapperTest {
public class HgGlobalConfigDtoToHgGlobalConfigMapperTest {
@InjectMocks
private HgConfigDtoToHgConfigMapperImpl mapper;
private HgGlobalConfigDtoToHgConfigMapperImpl mapper;
@Test
public void shouldMapFields() {
HgConfigDto dto = createDefaultDto();
HgConfig config = mapper.map(dto);
HgGlobalGlobalConfigDto dto = createDefaultDto();
HgGlobalConfig config = mapper.map(dto);
assertTrue(config.isDisabled());
@@ -52,8 +52,8 @@ public class HgConfigDtoToHgConfigMapperTest {
assertTrue(config.isEnableHttpPostArgs());
}
private HgConfigDto createDefaultDto() {
HgConfigDto configDto = new HgConfigDto();
private HgGlobalGlobalConfigDto createDefaultDto() {
HgGlobalGlobalConfigDto configDto = new HgGlobalGlobalConfigDto();
configDto.setDisabled(true);
configDto.setEncoding("ABC");
configDto.setHgBinary("/etc/hg");

View File

@@ -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.fasterxml.jackson.databind.ObjectMapper;
@@ -42,20 +42,20 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini")
public class HgConfigInIndexResourceTest {
public class HgGlobalConfigInIndexResourceTest {
@Rule
public final ShiroRule shiroRule = new ShiroRule();
private final ObjectMapper objectMapper = new ObjectMapper();
private final ObjectNode root = objectMapper.createObjectNode();
private final HgConfigInIndexResource hgConfigInIndexResource;
private final HgGlobalConfigInIndexResource hgGlobalConfigInIndexResource;
public HgConfigInIndexResourceTest() {
public HgGlobalConfigInIndexResourceTest() {
root.put("_links", objectMapper.createObjectNode());
ScmPathInfoStore pathInfoStore = new ScmPathInfoStore();
pathInfoStore.set(() -> URI.create("/"));
hgConfigInIndexResource = new HgConfigInIndexResource(Providers.of(pathInfoStore), objectMapper);
hgGlobalConfigInIndexResource = new HgGlobalConfigInIndexResource(Providers.of(pathInfoStore), objectMapper);
}
@Test
@@ -63,7 +63,7 @@ public class HgConfigInIndexResourceTest {
public void admin() {
JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
hgConfigInIndexResource.enrich(context);
hgGlobalConfigInIndexResource.enrich(context);
assertEquals("/v2/config/hg", root.get("_links").get("hgConfig").get("href").asText());
}
@@ -73,7 +73,7 @@ public class HgConfigInIndexResourceTest {
public void user() {
JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
hgConfigInIndexResource.enrich(context);
hgGlobalConfigInIndexResource.enrich(context);
assertTrue(root.get("_links").iterator().hasNext());
}
@@ -82,7 +82,7 @@ public class HgConfigInIndexResourceTest {
public void anonymous() {
JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
hgConfigInIndexResource.enrich(context);
hgGlobalConfigInIndexResource.enrich(context);
assertFalse(root.get("_links").iterator().hasNext());
}

View File

@@ -24,18 +24,18 @@
package sonia.scm.api.v2.resources;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
class HgConfigTests {
class HgGlobalConfigTestUtil {
private HgConfigTests() {
private HgGlobalConfigTestUtil() {
}
static HgConfig createConfiguration() {
HgConfig config = new HgConfig();
static HgGlobalConfig createConfiguration() {
HgGlobalConfig config = new HgGlobalConfig();
config.setDisabled(true);
config.setEncoding("ABC");
@@ -45,7 +45,7 @@ class HgConfigTests {
return config;
}
static void assertEqualsConfiguration(HgConfigDto dto) {
static void assertEqualsConfiguration(HgGlobalGlobalConfigDto dto) {
assertTrue(dto.isDisabled());
assertEquals("ABC", dto.getEncoding());

View File

@@ -0,0 +1,101 @@
/*
* 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 org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mapstruct.factory.Mappers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.HgGlobalConfig;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static sonia.scm.api.v2.resources.HgGlobalConfigTestUtil.assertEqualsConfiguration;
import static sonia.scm.api.v2.resources.HgGlobalConfigTestUtil.createConfiguration;
@ExtendWith(MockitoExtension.class)
class HgGlobalConfigToHgGlobalConfigDtoMapperTest {
private final URI baseUri = URI.create("http://example.com/base/");
private final URI expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2);
@Mock
private Subject subject;
private HgGlobalConfigToHgGlobalConfigDtoMapper mapper;
@BeforeEach
void init() {
ThreadContext.bind(subject);
ScmPathInfoStore store = new ScmPathInfoStore();
store.set(() -> baseUri);
mapper = Mappers.getMapper(HgGlobalConfigToHgGlobalConfigDtoMapper.class);
mapper.setLinks(new HgConfigLinks(store));
}
@AfterEach
void unbindSubject() {
ThreadContext.unbindSubject();
}
@Test
void shouldMapFields() {
HgGlobalConfig config = createConfiguration();
when(subject.isPermitted("configuration:write:hg")).thenReturn(true);
HgGlobalGlobalConfigDto dto = mapper.map(config);
assertEqualsConfiguration(dto);
assertThat(dto.getLinks().getLinkBy("self")).hasValueSatisfying(
link -> assertThat(link.getHref()).isEqualTo(expectedBaseUri.toString())
);
assertThat(dto.getLinks().getLinkBy("update")).hasValueSatisfying(
link -> assertThat(link.getHref()).isEqualTo(expectedBaseUri.toString())
);
}
@Test
void shouldMapFieldsWithoutUpdate() {
HgGlobalConfig config = createConfiguration();
when(subject.isPermitted("configuration:write:hg")).thenReturn(false);
HgGlobalGlobalConfigDto dto = mapper.map(config);
assertThat(dto.getLinks().getLinkBy("self")).hasValueSatisfying(
link -> assertThat(link.getHref()).isEqualTo(expectedBaseUri.toString())
);
assertThat(dto.getLinks().hasLink("update")).isFalse();
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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 org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryTestData;
import java.net.URI;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class HgRepositoryConfigEnricherTest {
private HgRepositoryConfigEnricher enricher;
@Mock
private HalEnricherContext context;
@Mock
private HalAppender appender;
@BeforeEach
void setUp() {
ScmPathInfoStore store = new ScmPathInfoStore();
store.set(() -> URI.create("/"));
HgConfigLinks links = new HgConfigLinks(store);
enricher = new HgRepositoryConfigEnricher(links);
}
@Test
void shouldEnrichHgRepository() {
addRepositoryToContext("hg");
enricher.enrich(context, appender);
verify(appender).appendLink("configuration", "/v2/config/hg/hitchhiker/HeartOfGold");
}
@Test
void shouldNotEnrichNonHgRepository() {
addRepositoryToContext("git");
enricher.enrich(context, appender);
verify(appender, never()).appendLink(anyString(), anyString());
}
private void addRepositoryToContext(String type) {
Repository repository = RepositoryTestData.createHeartOfGold(type);
when(context.oneRequireByType(Repository.class)).thenReturn(repository);
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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 org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.HgRepositoryConfig;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryTestData;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HgRepositoryConfigMapperTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private HgConfigLinks links;
@InjectMocks
private HgRepositoryConfigMapperImpl mapper;
@Mock
private Subject subject;
@BeforeEach
void setUpSubject() {
ThreadContext.bind(subject);
}
@AfterEach
void tearDownSubject() {
ThreadContext.unbindSubject();
}
@Test
void shouldMapToDto() {
Repository repository = RepositoryTestData.createHeartOfGold("hg");
when(links.repository(repository).get()).thenReturn("/hg/config");
HgRepositoryConfig config = new HgRepositoryConfig();
config.setEncoding("UTF-8");
HgRepositoryConfigDto dto = mapper.map(repository, config);
assertThat(dto.getEncoding()).isEqualTo("UTF-8");
assertThat(dto.getLinks().getLinkBy("self")).hasValueSatisfying(
link -> assertThat(link.getHref()).isEqualTo("/hg/config")
);
}
@Test
void shouldAppendUpdateLink() {
Repository repository = RepositoryTestData.createHeartOfGold("hg");
when(links.repository(repository).get()).thenReturn("/hg/config");
when(links.repository(repository).update()).thenReturn("/hg/config/update");
when(subject.isPermitted("repository:hg:" + repository.getId())).thenReturn(true);
HgRepositoryConfigDto dto = mapper.map(repository, new HgRepositoryConfig());
assertThat(dto.getLinks().getLinkBy("update")).hasValueSatisfying(
link -> assertThat(link.getHref()).isEqualTo("/hg/config/update")
);
}
}

View File

@@ -0,0 +1,165 @@
/*
* 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 com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.util.Providers;
import de.otto.edison.hal.Links;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mapstruct.factory.Mappers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.HgRepositoryConfig;
import sonia.scm.repository.HgRepositoryConfigStore;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.web.HgVndMediaType;
import sonia.scm.web.RestDispatcher;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class HgRepositoryConfigResourceTest {
private final ObjectMapper objectMapper = new ObjectMapper();
private RestDispatcher dispatcher;
@Mock
private RepositoryManager repositoryManager;
@Mock
private HgRepositoryConfigStore store;
@Mock
private Subject subject;
@BeforeEach
void setUp() {
ThreadContext.bind(subject);
HgRepositoryConfigMapper mapper = createConfigMapper();
dispatcher = new RestDispatcher();
dispatcher.addSingletonResource(createRootResource(
new HgRepositoryConfigResource(repositoryManager, store, mapper)
));
}
@AfterEach
void tearDownSubject() {
ThreadContext.unbindSubject();
}
private HgConfigResource createRootResource(HgRepositoryConfigResource resource) {
return new HgConfigResource(
mock(HgGlobalConfigDtoToHgConfigMapper.class),
mock(HgGlobalConfigToHgGlobalConfigDtoMapper.class),
mock(HgRepositoryHandler.class),
Providers.of(mock(HgGlobalConfigAutoConfigurationResource.class)),
Providers.of(resource)
);
}
private HgRepositoryConfigMapper createConfigMapper() {
ScmPathInfoStore pathInfoStore = new ScmPathInfoStore();
pathInfoStore.set(() -> URI.create("/"));
HgConfigLinks links = new HgConfigLinks(pathInfoStore);
HgRepositoryConfigMapper mapper = Mappers.getMapper(HgRepositoryConfigMapper.class);
mapper.setLinks(links);
return mapper;
}
@Test
void shouldGetConfig() throws IOException, URISyntaxException {
HgRepositoryConfig config = new HgRepositoryConfig();
config.setEncoding("ISO-8859-15");
Repository repository = RepositoryTestData.createHeartOfGold("hg");
when(repositoryManager.get(new NamespaceAndName("hitchhiker", "hog"))).thenReturn(repository);
when(store.of(repository)).thenReturn(config);
MockHttpRequest request = MockHttpRequest.get("/v2/config/hg/hitchhiker/hog");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
JsonNode node = objectMapper.readTree(response.getContentAsString());
assertThat(node.get("encoding").asText()).isEqualTo("ISO-8859-15");
assertThat(node.get("_links").get("self").get("href").asText()).isEqualTo("/v2/config/hg/hitchhiker/HeartOfGold");
}
@Test
void shouldUpdateConfig() throws IOException, URISyntaxException {
Repository repository = RepositoryTestData.createHeartOfGold("hg");
when(repositoryManager.get(new NamespaceAndName("hitchhiker", "hog"))).thenReturn(repository);
HgRepositoryConfigDto dto = new HgRepositoryConfigDto(Links.emptyLinks());
dto.setEncoding("UTF-8");
MockHttpRequest request = MockHttpRequest.put("/v2/config/hg/hitchhiker/hog").contentType(
HgVndMediaType.REPO_CONFIG
).content(objectMapper.writeValueAsBytes(dto));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
ArgumentCaptor<HgRepositoryConfig> captor = ArgumentCaptor.forClass(HgRepositoryConfig.class);
verify(store).store(eq(repository), captor.capture());
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NO_CONTENT);
assertThat(captor.getValue().getEncoding()).isEqualTo("UTF-8");
}
@Test
void shouldFailWithInvalidEncoding() throws IOException, URISyntaxException {
HgRepositoryConfigDto dto = new HgRepositoryConfigDto(Links.emptyLinks());
dto.setEncoding("XA");
MockHttpRequest request = MockHttpRequest.put("/v2/config/hg/hitchhiker/hog")
.contentType(HgVndMediaType.REPO_CONFIG).content(objectMapper.writeValueAsBytes(dto));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
}
}

View File

@@ -32,7 +32,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgVerifier;
import java.io.File;
@@ -57,7 +57,7 @@ class PosixAutoConfiguratorTest {
PosixAutoConfigurator configurator = create(directory);
HgConfig config = new HgConfig();
HgGlobalConfig config = new HgGlobalConfig();
configurator.configure(config);
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
@@ -84,7 +84,7 @@ class PosixAutoConfiguratorTest {
verifier, createEnv(def), ImmutableList.of(additional.toAbsolutePath().toString())
);
HgConfig config = new HgConfig();
HgGlobalConfig config = new HgGlobalConfig();
configurator.configure(config);
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
@@ -109,7 +109,7 @@ class PosixAutoConfiguratorTest {
)
);
HgConfig config = new HgConfig();
HgGlobalConfig config = new HgGlobalConfig();
configurator.configure(config);
assertThat(config.getHgBinary()).isEqualTo(hg.toString());
@@ -119,7 +119,7 @@ class PosixAutoConfiguratorTest {
void shouldNotConfigureMercurial(@TempDir Path directory) {
PosixAutoConfigurator configurator = create(directory);
HgConfig config = new HgConfig();
HgGlobalConfig config = new HgGlobalConfig();
configurator.configure(config);
assertThat(config.getHgBinary()).isNull();

View File

@@ -31,7 +31,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgVerifier;
import java.io.File;
@@ -129,7 +129,7 @@ class WindowsAutoConfiguratorTest {
}
private String configure(String path) {
HgConfig config = new HgConfig();
HgGlobalConfig config = new HgGlobalConfig();
configurator(path).configure(config);
return config.getHgBinary();
}

View File

@@ -42,7 +42,6 @@ import sonia.scm.security.CipherUtil;
import sonia.scm.security.Xsrf;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
@@ -60,7 +59,7 @@ class DefaultHgEnvironmentBuilderTest {
private AccessTokenBuilderFactory accessTokenBuilderFactory;
@Mock
private HgRepositoryHandler repositoryHandler;
private HgConfigResolver repositoryConfigResolver;
@Mock
private HookEnvironment hookEnvironment;
@@ -71,6 +70,9 @@ class DefaultHgEnvironmentBuilderTest {
@InjectMocks
private DefaultHgEnvironmentBuilder builder;
@Mock
private HgConfig config;
private Path directory;
@BeforeEach
@@ -141,14 +143,12 @@ class DefaultHgEnvironmentBuilderTest {
@Nonnull
private Repository prepareForRead(String id) {
when(repositoryHandler.getDirectory(id)).thenReturn(directory.resolve("repo").toFile());
HgConfig config = new HgConfig();
when(repositoryHandler.getConfig()).thenReturn(config);
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
heartOfGold.setId(id);
when(repositoryConfigResolver.resolve(heartOfGold)).thenReturn(config);
when(config.getDirectory()).thenReturn(directory.resolve("repo").toFile());
return heartOfGold;
}

View File

@@ -0,0 +1,85 @@
/*
* 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 org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HgConfigResolverTest {
@Mock
private HgRepositoryHandler handler;
@Mock
private HgRepositoryConfigStore repositoryConfigStore;
private HgConfigResolver resolver;
private Repository heartOfGold = RepositoryTestData.createHeartOfGold("hg");
private HgGlobalConfig globalConfig;
private HgRepositoryConfig repositoryConfig;
@BeforeEach
void setUpResolver(@TempDir Path directory) {
globalConfig = new HgGlobalConfig();
repositoryConfig = new HgRepositoryConfig();
when(handler.getDirectory(heartOfGold.getId())).thenReturn(directory.toFile());
when(handler.getConfig()).thenReturn(globalConfig);
when(repositoryConfigStore.of(heartOfGold)).thenReturn(repositoryConfig);
resolver = new HgConfigResolver(handler, repositoryConfigStore);
}
@Test
void shouldReturnEncodingFromRepositoryConfig() {
globalConfig.setEncoding("ISO-8859-1");
repositoryConfig.setEncoding("ISO-8859-15");
HgConfig config = resolver.resolve(heartOfGold);
assertThat(config.getEncoding()).isEqualTo("ISO-8859-15");
}
@Test
void shouldReturnEncodingFromGlobalConfig() {
globalConfig.setEncoding("ISO-8859-1");
HgConfig config = resolver.resolve(heartOfGold);
assertThat(config.getEncoding()).isEqualTo("ISO-8859-1");
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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 org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.store.InMemoryConfigurationStoreFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HgRepositoryConfigStoreTest {
@Mock
private Subject subject;
private HgRepositoryConfigStore store;
@BeforeEach
void setUp() {
ThreadContext.bind(subject);
store = new HgRepositoryConfigStore(new InMemoryConfigurationStoreFactory());
}
@AfterEach
void tearDownSubject() {
ThreadContext.unbindSubject();
}
@Test
void shouldThrowAuthorizationException() {
Repository repository = RepositoryTestData.createHeartOfGold("hg");
doThrow(AuthorizationException.class)
.when(subject)
.checkPermission("repository:hg:" + repository.getId());
HgRepositoryConfig config = new HgRepositoryConfig();
assertThrows(AuthorizationException.class, () -> store.store(repository, config));
}
@Test
void shouldReadAndStore() {
Repository repository = RepositoryTestData.createHeartOfGold("hg");
HgRepositoryConfig config = store.of(repository);
config.setEncoding("ISO-8859-15");
store.store(repository, config);
assertThat(store.of(repository).getEncoding()).isEqualTo("ISO-8859-15");
}
}

View File

@@ -63,7 +63,8 @@ class HgRepositoryFactoryTest {
handler = HgTestUtil.createHandler(directory.toFile());
assumeTrue(handler.isConfigured());
factory = new HgRepositoryFactory(handler, hookEnvironment, environmentBuilder);
HgConfigResolver resolver = new HgConfigResolver(handler);
factory = new HgRepositoryFactory(resolver, hookEnvironment, environmentBuilder);
heartOfGold = createRepository();
}

View File

@@ -85,9 +85,9 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
public void getDirectory() {
HgRepositoryHandler repositoryHandler = createHandler(factory, locationResolver);
HgConfig hgConfig = new HgConfig();
hgConfig.setHgBinary("hg");
repositoryHandler.setConfig(hgConfig);
HgGlobalConfig hgGlobalConfig = new HgGlobalConfig();
hgGlobalConfig.setHgBinary("hg");
repositoryHandler.setConfig(hgGlobalConfig);
initRepository();
File path = repositoryHandler.getDirectory(repository.getId());

View File

@@ -41,38 +41,32 @@ import static org.mockito.Mockito.mock;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
public final class HgTestUtil
{
public final class HgTestUtil {
/**
* Constructs ...
*
*/
private HgTestUtil() {}
private HgTestUtil() {
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param handler
*/
public static void checkForSkip(HgRepositoryHandler handler)
{
public static void checkForSkip(HgRepositoryHandler handler) {
// skip tests if hg not in path
if (!handler.isConfigured())
{
if (!handler.isConfigured()) {
System.out.println("WARNING could not find hg, skipping test");
Assume.assumeTrue(false);
}
if (Boolean.getBoolean("sonia.scm.test.skip.hg"))
{
if (Boolean.getBoolean("sonia.scm.test.skip.hg")) {
System.out.println("WARNING mercurial test are disabled");
Assume.assumeTrue(false);
}
@@ -97,8 +91,9 @@ public final class HgTestUtil
}
public static HgRepositoryFactory createFactory(HgRepositoryHandler handler, File directory) {
HgConfigResolver resolver = new HgConfigResolver(handler, repository -> directory);
return new HgRepositoryFactory(
handler, new HookEnvironment(), new EmptyHgEnvironmentBuilder(), repository -> directory
resolver, new HookEnvironment(), new EmptyHgEnvironmentBuilder()
);
}
}

View File

@@ -107,7 +107,7 @@ class HgVerifierTest {
}
private boolean verify(Path hg) {
HgConfig config = new HgConfig();
HgGlobalConfig config = new HgGlobalConfig();
config.setHgBinary(hg.toString());
return verifier.isValid(config);
}

View File

@@ -29,16 +29,15 @@ package sonia.scm.repository.spi;
import org.junit.After;
import org.junit.Before;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgRepositoryFactory;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.HgTestUtil;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.repository.hooks.HookEnvironment;
import sonia.scm.util.MockUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
/**
@@ -66,8 +65,10 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase
this.handler = HgTestUtil.createHandler(tempFolder.newFolder());
HgTestUtil.checkForSkip(handler);
HgConfigResolver resolver = new HgConfigResolver(handler);
HgRepositoryFactory factory = HgTestUtil.createFactory(handler, repositoryDirectory);
cmdContext = new HgCommandContext(handler, factory, RepositoryTestData.createHeartOfGold());
cmdContext = new HgCommandContext(resolver, factory, RepositoryTestData.createHeartOfGold());
}
//~--- set methods ----------------------------------------------------------

View File

@@ -29,6 +29,7 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Changeset;
import org.junit.Test;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgTestUtil;
import sonia.scm.repository.InternalRepositoryException;
@@ -111,8 +112,9 @@ public class HgIncomingCommandTest extends IncomingOutgoingTestBase
}
private HgIncomingCommand createIncomingCommand() {
HgConfigResolver resolver = new HgConfigResolver(handler);
return new HgIncomingCommand(
new HgCommandContext(handler, HgTestUtil.createFactory(handler, incomingDirectory), incomingRepository),
new HgCommandContext(resolver, HgTestUtil.createFactory(handler, incomingDirectory), incomingRepository),
handler
);
}

View File

@@ -30,6 +30,7 @@ import com.aragost.javahg.commands.RemoveCommand;
import com.aragost.javahg.commands.RenameCommand;
import org.junit.Before;
import org.junit.Test;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgTestUtil;
import sonia.scm.repository.Modifications;
@@ -44,7 +45,8 @@ public class HgModificationsCommandTest extends IncomingOutgoingTestBase {
@Before
public void init() {
HgCommandContext outgoingContext = new HgCommandContext(handler, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository);
HgConfigResolver configResolver = new HgConfigResolver(handler);
HgCommandContext outgoingContext = new HgCommandContext(configResolver, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository);
outgoingModificationsCommand = new HgModificationsCommand(outgoingContext);
}

View File

@@ -29,6 +29,7 @@ package sonia.scm.repository.spi;
import com.aragost.javahg.Changeset;
import org.junit.Test;
import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.HgConfigResolver;
import sonia.scm.repository.HgTestUtil;
import sonia.scm.repository.InternalRepositoryException;
@@ -107,8 +108,9 @@ public class HgOutgoingCommandTest extends IncomingOutgoingTestBase
}
private HgOutgoingCommand createOutgoingCommand() {
HgConfigResolver resolver = new HgConfigResolver(handler);
return new HgOutgoingCommand(
new HgCommandContext(handler, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository),
new HgCommandContext(resolver, HgTestUtil.createFactory(handler, outgoingDirectory), outgoingRepository),
handler
);
}

View File

@@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.HgTestUtil;
@@ -54,7 +54,7 @@ class HgVersionCommandTest {
@Test
void shouldReturnUnknownForIOException() {
HgVersionCommand command = new HgVersionCommand(new HgConfig(), "/i/dont/know", cmd -> {
HgVersionCommand command = new HgVersionCommand(new HgGlobalConfig(), "/i/dont/know", cmd -> {
throw new IOException("failed");
});

View File

@@ -38,7 +38,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import sonia.scm.AbstractTestBase;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.HgTestUtil;
import sonia.scm.user.User;
@@ -182,7 +182,7 @@ public abstract class IncomingOutgoingTestBase extends AbstractTestBase
*/
private RepositoryConfiguration createConfig(HgRepositoryHandler handler)
{
HgConfig cfg = handler.getConfig();
HgGlobalConfig cfg = handler.getConfig();
RepositoryConfiguration configuration = RepositoryConfiguration.DEFAULT;
configuration.setHgBin(cfg.getHgBinary());

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.web;
import org.junit.Before;
@@ -32,7 +32,7 @@ import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgGlobalConfig;
import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.RepositoryProvider;
@@ -72,7 +72,7 @@ public class HgPermissionFilterTest {
@Before
public void setUp() {
when(hgRepositoryHandler.getConfig()).thenReturn(new HgConfig());
when(hgRepositoryHandler.getConfig()).thenReturn(new HgGlobalConfig());
}
/**
@@ -82,9 +82,9 @@ public class HgPermissionFilterTest {
public void testWrapRequestIfRequired() {
assertSame(request, filter.wrapRequestIfRequired(request));
HgConfig hgConfig = new HgConfig();
hgConfig.setEnableHttpPostArgs(true);
when(hgRepositoryHandler.getConfig()).thenReturn(hgConfig);
HgGlobalConfig hgGlobalConfig = new HgGlobalConfig();
hgGlobalConfig.setEnableHttpPostArgs(true);
when(hgRepositoryHandler.getConfig()).thenReturn(hgGlobalConfig);
assertThat(filter.wrapRequestIfRequired(request), is(instanceOf(HgServletRequest.class)));
}
@@ -112,7 +112,7 @@ public class HgPermissionFilterTest {
*/
@Test
public void testIsWriteRequestWithEnabledHttpPostArgs() {
HgConfig config = new HgConfig();
HgGlobalConfig config = new HgGlobalConfig();
config.setEnableHttpPostArgs(true);
when(hgRepositoryHandler.getConfig()).thenReturn(config);